156 Commits
Author SHA1 Message Date
Samuel Attard 0e5d146ba1 align terminationHandler with dealloc in SQRLZipArchiver (#259)
For some reason even though the `NSTask` is terminated the file handle is still being read.  Fixes #247

Verified locally using the electron test suite and `chown nobody:nogroup Electron.app` during the test suite
2021-06-18 15:08:36 -07:00
Devin Foley cdc0729c8b fix: try multiple times to get a writeable log file (#255) 2020-11-03 13:05:04 -08:00
Samuel Attard a3a5b3f03b fix: use new ShipIt binary on macOS 11.0 to workaround flaw in launchApplicationAtURL (#253)
* fix: use /usr/bin/open on macOS 11.0 to workaround flaw in launchApplicationAtURL

* Use the new ShipIt as the launch pad

* chore: clean up diff

* Update cibuild

* build: use xcode 9.4
2020-10-09 15:52:19 -07:00
Samuel Attard 44468f858c chore: fix missing deallocs, deprecation warnings and invalid casts (#249) 2020-06-29 16:03:32 -07:00
Devin Foley adbe40a86a Set a 20 minute download timeout. (#241) 2019-11-05 13:29:29 -08:00
Samuel Attard b5ce589c4c Revert "fix: link ShipIt to ReactiveCocoa and Mantle correctly (#235)"
This reverts commit b426994d83.
2019-05-15 19:13:30 -07:00
Samuel Attard b426994d83 fix: link ShipIt to ReactiveCocoa and Mantle correctly (#235) 2019-03-25 13:49:42 -07:00
Josh Abernathy 279b5f9eb8 Merge pull request #231 from olegsklyarov/patch-1
fix jump link to update-file-json-format
2018-10-26 12:13:53 -04:00
Oleg Sklyarov 20a17327a6 fix jump link to update-file-json-format 2018-10-26 18:27:47 +03:00
Josh Abernathy 2a001cc575 Merge pull request #227 from tonyganch/xcode_build
Fix build for Xcode 9
2018-07-02 09:40:21 -04:00
Josh Abernathy 9a763fdab6 Merge pull request #225 from tonyganch/patch-1
Update README.md
2018-07-02 09:23:49 -04:00
Tony Ganch c3e3bddac9 Fix build for Xcode 9 2018-07-02 09:16:26 +02:00
Tony Ganch 3b419eb2e3 Update README.md
Reflect correct path to local checkouts of ReactiveCocoa and Mantle.
2018-06-01 21:02:38 +02:00
Josh Abernathy cb838776ce Merge pull request #221 from MarshallOfSound/patch-1
Fix invalid JSON in readme
2018-02-05 05:53:30 -10:00
Samuel Attard 8a5c88c9fa Fix invalid JSON in readme 2018-01-13 14:35:26 +11:00
Josh Abernathy 91f86ea8da Merge pull request #219 from MarshallOfSound/cdn-releases-fixed
Add CDN based releases
2018-01-12 16:27:36 -05:00
Samuel Attard 64c3fbe198 remove timeout 2018-01-12 15:34:19 +11:00
Thomas Moenicke 9a30a196ec submodules as in master 2018-01-11 02:09:03 +11:00
Samuel Attard 2abaad19b6 Document JSON structure 2018-01-11 02:07:27 +11:00
Samuel Attard 7e5dab08d1 Code cleanup 2018-01-11 02:01:38 +11:00
Samuel Attard d7233e04a3 Handle bad JSON and fix usage of NSURLRequest 2018-01-10 11:44:08 +11:00
Thomas Moenicke c80f006649 keep existing signatures for initWithUpdateRequest 2018-01-10 11:44:08 +11:00
Thomas Moenicke 23f184c1dd cleanup 2018-01-10 11:44:08 +11:00
Thomas Moenicke db58a91497 adding api to init SQRLi to init in CDN mode 2018-01-10 11:44:08 +11:00
Thomas Moenicke 3f398df659 using env variables instead of query parameter to switch between release server and json file mode
use: MODE=JSONFILE VERSION=1.5.0 open MyApp.app
defaults to release server
2018-01-10 11:44:08 +11:00
Thomas Moenicke 8536201df9 removed support for text based releases file
SquirrelApp is now an external project
2018-01-10 11:44:08 +11:00
Thomas Moenicke 417f4d8e91 adding cdn-based updates: a file (simple text or json) containing release information together with release files on a cdn 2018-01-10 11:44:08 +11:00
Josh Abernathy 3053fa59e2 Merge pull request #217 from pornel/master
Suggest only secure URLs for app download
2017-10-27 08:19:38 -04:00
Kornel 1d2388b30f Suggest only secure URLs for app download 2017-10-27 00:21:14 +01:00
Josh Abernathy 5396dcd1f9 Merge pull request #210 from Squirrel/provide-more-error-info
Provide more error context when we get POSIX errors
2017-07-20 09:47:13 -04:00
Josh Abernathy 203315f93e Merge pull request #207 from Squirrel/escalators-are-dangerous
Fix installing updates which need escalated privileges
2017-07-20 09:46:47 -04:00
joshaber 523824ea57 Provide more error context when we get POSIX errors
Fixes #209
2017-07-20 08:59:57 -04:00
joshaber 306443aaaf Merge branch 'master' into escalators-are-dangerous 2017-07-19 12:39:31 -04:00
Josh Abernathy db17e6865f Merge pull request #208 from Squirrel/make-tests-happen
Make CI happen
2017-07-19 12:39:01 -04:00
joshaber d14376c277 Don't run tests 2017-07-19 11:51:24 -04:00
joshaber 70cdda1ea2 Update the fixture app 2017-07-18 17:26:43 -04:00
joshaber 168fc856ff Update the expected error 2017-07-18 17:26:43 -04:00
joshaber f83f4ddcb7 I guess we need to do this to make signing work? 2017-07-18 17:26:43 -04:00
joshaber 7d02f612f8 Merge branch 'master' into escalators-are-dangerous 2017-07-18 13:47:56 -04:00
Josh Abernathy 0d4d0ba602 Merge pull request #206 from Squirrel/update-xcode
Update to Xcode 8
2017-07-18 13:47:33 -04:00
joshaber 6f16206cef I dunno try this 2017-07-18 13:02:21 -04:00
joshaber ee89952cc4 Revert "I dunno try this"
This reverts commit 6b98348ceb.
2017-07-18 13:01:43 -04:00
joshaber 6b98348ceb I dunno try this 2017-07-18 13:01:30 -04:00
joshaber b63125bfe0 Don't need this import either 2017-07-18 12:52:13 -04:00
joshaber 5ca2018292 Hardcode this 2017-07-18 11:20:03 -04:00
joshaber bbb1edb0fb Separate building and testing 2017-07-18 10:47:50 -04:00
joshaber b98c5981af Use xcodebuild to build 2017-07-18 09:58:57 -04:00
joshaber 13c6951b35 Consume the ShipIt state path from the args 2017-07-17 17:34:05 -04:00
joshaber 8806258058 Pass the state path to ShipIt 2017-07-17 17:33:52 -04:00
joshaber e672a408f4 Merge branch 'update-xcode' into fix-permissions 2017-07-17 17:05:19 -04:00
joshaber 386b7c09af Update the Travis image too 2017-07-17 17:04:13 -04:00
joshaber 27dedad85d Check writability for the parent directory too
If the parent isn't writable (e.g., /Applications), we'll need to launch with escalated privileges.
2017-07-17 17:04:00 -04:00
joshaber c22ad09d8e Update our dependencies 2017-07-17 16:51:09 -04:00
joshaber 9f87ff2113 Update the project and schemes 2017-07-17 16:45:23 -04:00
Josh Abernathy bde5ff2983 Merge pull request #190 from slyngbaek/master
Add auth headers to zip download requests not just feed requests.
2016-11-08 10:38:28 -05:00
Steffen Lyngbaek ae78589d3a Update block type name 2016-11-07 14:56:36 -08:00
Steffen Lyngbaek abec74ed3f Add block to initializer to provide custom download requests.
Add a second initializer with with a block argument to handle
customization of download url requests.

Add a generic download request to the original initializer without
changing the current api.
2016-11-07 12:19:02 -08:00
Steffen Lyngbaek a994876d98 Don't clobber headers explicitly set on the zip download request. 2016-11-04 10:32:54 -07:00
Steffen Lyngbaek 9d5464c693 Add auth headers to zip download requests not just feed requests. 2016-11-03 14:14:51 -07:00
Keith Duncan f00ad7e91b Merge pull request #186 from Squirrel/dont-check-for-updates-when-readonly
Error if there's an update available but the app's on a read-only volume
2016-10-18 11:03:53 +11:00
joshaber 4b0d5ffe18 Error check the statfs call 2016-10-17 14:04:20 -04:00
joshaber d93abe66ec Use a better description 2016-10-17 14:04:09 -04:00
joshaber 196abf93b8 Use a unique error code 2016-10-17 14:03:59 -04:00
joshaber a0079fdee0 Only error once we know if it matters 2016-10-14 10:47:24 -04:00
joshaber fdb4ffa000 Throw an error instead of silently failing 2016-10-14 10:43:56 -04:00
joshaber cc047bdc6f Get outta town if the volume's readonly 2016-10-14 10:43:09 -04:00
joshaber 7231919408 Added -isRunningOnReadOnlyVolume 2016-10-14 10:40:33 -04:00
Josh Abernathy e9e2188cda Merge pull request #176 from Squirrel/fix-updates-again
Don't error if our conditional GET fails.
2016-06-15 08:05:34 -07:00
joshaber 413b1d2bca Reorganize to separate out the unarchiving 2016-06-14 15:37:24 -07:00
joshaber d92d385431 Don't error if our conditional GET fails. 2016-06-13 18:09:24 -07:00
Josh Abernathy 9ebfd397fd Merge pull request #175 from Squirrel/fix-pruning
Don't prune if we've already downloaded updates.
2016-06-12 11:28:48 -04:00
joshaber 7dffc4b143 Don't prune if we've already downloaded updates.
Fixes #174
2016-06-08 14:30:37 -04:00
Josh Abernathy 37cd028730 Merge pull request #172 from Squirrel/truncate-logs
Truncate logs
2016-06-03 12:43:44 -04:00
joshaber cc8993e6e7 Bump up the max length. 2016-06-03 09:55:05 -04:00
joshaber a0b35f490b Revert "Move back to the beginning of the file before writing."
This reverts commit ed0614d8cf.
2016-06-02 12:32:29 -04:00
joshaber ed0614d8cf Move back to the beginning of the file before writing. 2016-06-02 12:31:03 -04:00
joshaber ea70eb937a Truncate log files. 2016-06-02 12:27:21 -04:00
joshaber b5ecff7e03 Use the log paths instead of constructing them manually. 2016-06-02 12:27:11 -04:00
joshaber 8589cbedbb Expose the log paths. 2016-06-02 12:27:02 -04:00
Josh Abernathy edc604bfec Merge pull request #171 from Squirrel/stop-redownloading
Conditionally download updates.
2016-06-02 10:13:22 -04:00
Keith Duncan 8995e0faeb Merge pull request #169 from Squirrel/use-cache
Use the Caches directory instead of Application Support.
2016-06-02 16:07:17 +02:00
joshaber 67a68bef8c 🍩 need this change 2016-06-01 14:26:06 -04:00
joshaber 7c4b43d2d1 Conditionally download the update. 2016-06-01 14:20:04 -04:00
Josh Abernathy 694f96abec More words.
Fixes #120.
2016-06-01 13:49:01 -04:00
joshaber 369854b103 Update specs 2016-06-01 13:12:24 -04:00
joshaber 104ebb9442 s/an/a 2016-06-01 12:59:49 -04:00
joshaber e1f56bfdc6 Use the Caches directory instead of Application Support. 2016-06-01 12:55:24 -04:00
Keith Duncan 4a50411246 Merge pull request #165 from adam-lynch/patch-1
README: removing trailing comma from JSON format
2016-02-22 10:06:24 +11:00
Adam Lynch fc3ce8158c README: removing trailing comma from JSON format 2016-02-21 22:54:01 +00:00
Josh Abernathy 6e1e11809e Merge pull request #156 from Squirrel/dates
Fix parsing of ISO 8601 dates
2015-11-09 13:25:16 -05:00
Matt Diephouse 9d67b82d5c Use the correct day specifier
`DD` is day of year; `dd` is day of month.
2015-11-09 12:58:40 -05:00
Matt Diephouse 09a3b74f93 Test the dates are correct 2015-11-09 12:57:41 -05:00
Matt Diephouse c108f4aac4 Merge pull request #154 from Squirrel/fix-ci
Fix CI
2015-10-26 17:43:39 -04:00
joshaber 0ac595efd9 I am dissappoint, Travis 2015-10-26 17:25:51 -04:00
joshaber ce1b59c1cf Revert "Smore random sleeps"
This reverts commit 2dea99045d.
2015-10-26 17:17:55 -04:00
joshaber f7438e91b3 Revert "More sleeping!"
This reverts commit 656d2cdd29.
2015-10-26 17:17:46 -04:00
joshaber 656d2cdd29 More sleeping! 2015-10-26 17:08:03 -04:00
joshaber d8a244dd1a Try Xcode 7.1 for funsies. 2015-10-26 16:58:21 -04:00
joshaber 2dea99045d Smore random sleeps 2015-10-26 16:56:28 -04:00
joshaber 483eb38d73 Resource rules were deprecated. 2015-10-26 16:55:30 -04:00
joshaber 475c50a8e3 🔥 mdeep-codesign
It hasn’t been needed for a while.
2015-10-26 16:51:37 -04:00
joshaber 4384ccb485 I dunno let's try waiting. 2015-10-26 16:46:29 -04:00
joshaber 4b4bb7c87c Stop tailing 2015-10-26 16:36:46 -04:00
joshaber cd6ccb9c7b Try just one test. 2015-10-26 16:27:11 -04:00
joshaber 0c91d7fe9c Don't use async anymore. 2015-10-26 16:27:06 -04:00
joshaber 0d8fe0f642 LOL 2015-10-26 16:13:15 -04:00
joshaber b6f4e70731 Try an async. 2015-10-26 16:09:25 -04:00
joshaber efd944e337 Revert "can u not"
This reverts commit 5bfd32eeae.
2015-10-26 16:02:03 -04:00
joshaber cc86db04e2 Standardize on our longer timeout. 2015-10-26 16:01:45 -04:00
joshaber c9ce161632 Up the timeout. 2015-10-26 14:08:08 -04:00
joshaber f924fc3023 Make sure we don't use automatic termination. 2015-10-26 13:58:19 -04:00
joshaber 5bfd32eeae can u not 2015-10-26 13:26:54 -04:00
joshaber 7c365d304f Ugh spelling. 2015-10-26 13:06:31 -04:00
joshaber a6a782aefb Bigger timeout? 2015-10-26 12:59:31 -04:00
joshaber aa72c42c3b Setup some certs. 2015-10-26 12:21:33 -04:00
joshaber bee2be5dfb Revert "🔥 Deep code sign."
This reverts commit d0c932a90d.
2015-10-26 11:58:05 -04:00
joshaber 9bcc86be72 Update Travis again. 2015-10-26 11:42:43 -04:00
joshaber d0c932a90d 🔥 Deep code sign.
It’s been unnecessary for a while now, afaik. /cc @keithduncan
2015-10-26 11:41:40 -04:00
joshaber 4a0aa59d25 We really don't care about app extensions. 2015-10-26 11:40:49 -04:00
joshaber 70a838c834 Use Travis. 2015-10-23 16:52:54 -04:00
joshaber 19dc11c735 Fix the tests. 2015-10-23 16:52:50 -04:00
Matt Diephouse 87e854e8d4 Merge pull request #153 from Squirrel/xcode-7
Xcode 7 support
2015-10-23 10:45:12 -04:00
joshaber 5640bbe863 --regret 2015-10-23 09:49:49 -04:00
joshaber 843ee9fa22 Revert "Update all our references to RAC files."
This reverts commit 0026cef7bf.
2015-10-23 09:44:20 -04:00
joshaber 95ce084ddd Revert "Go with the swift2 branch of RAC."
This reverts commit 2cc8a7d4ab.
2015-10-23 09:44:12 -04:00
joshaber ed92f6aad2 Revert "Bleh."
This reverts commit 56f52ccfe4.
2015-10-23 09:44:02 -04:00
joshaber 56f52ccfe4 Bleh. 2015-10-22 17:19:17 -04:00
joshaber 2cc8a7d4ab Go with the swift2 branch of RAC. 2015-10-22 17:07:23 -04:00
joshaber 0026cef7bf Update all our references to RAC files. 2015-10-22 17:03:34 -04:00
joshaber f0fee4a4e8 ++RAC submodule 2015-10-22 16:41:45 -04:00
joshaber 1898bbde62 ++more things 2015-10-22 16:40:20 -04:00
joshaber 031a7cd850 ++everything 2015-10-22 16:31:59 -04:00
joshaber 27f7777ba5 Hi Xcode 7. 2015-10-22 16:17:57 -04:00
Matt Diephouse 8caa2fa200 Merge pull request #152 from Squirrel/fix-pruning
Fix pruning
2015-09-22 10:40:54 -05:00
joshaber 2fe45e940f Just use a method. 2015-09-22 00:21:23 +01:00
joshaber e50720470c Don't replay, that's crazy.
If we replay, then we don’t ever prune after the first subscription. We
just keep replaying the previous results.

Fixes https://github.com/atom/atom/issues/7061.
2015-09-21 00:17:11 +01:00
joshaber cebf4a85d6 Failing test. 2015-09-21 00:14:25 +01:00
joshaber 5f96d1e565 Allow tests to specify the number of update requests. 2015-09-21 00:14:08 +01:00
joshaber 1f3e0ccbe5 Verify that we have updates. 2015-09-21 00:10:30 +01:00
Keith Duncan 6252f3dbda Merge pull request #149 from Squirrel/renames
Renames
2015-06-29 17:09:59 +10:00
joshaber 1e71daa4f7 Merge branch 'renames-rebase' into renames
Conflicts:
	Squirrel/SQRLInstaller.m
2015-06-23 15:32:15 -04:00
joshaber ae89c0a1f6 Be more explicit. 2015-06-22 11:24:24 -04:00
joshaber d0634fc8e6 Naming and docs. 2015-06-19 13:48:31 -04:00
joshaber 178648f373 Let the abort use the old request. 2015-06-19 12:47:03 -04:00
joshaber 3a4428cfb3 Fix the tests. 2015-06-19 12:35:28 -04:00
joshaber dab5bf2892 Merge branch 'renames-rebase' into renames
Conflicts:
	Squirrel/SQRLInstaller.m
2015-06-19 12:31:46 -04:00
joshaber 221519e3ad Check the bundle ID at our new destination. 2015-06-18 13:58:38 -04:00
joshaber 7faed9ec18 Early exits. 2015-06-18 13:58:29 -04:00
joshaber c74ecf9c07 Use the source's name as the new name. 2015-06-18 13:47:47 -04:00
joshaber 04233f8f7c RAC em up. 2015-06-18 13:34:27 -04:00
joshaber 378e3891bf Try the dumb guy route first. 2015-06-18 12:27:59 -04:00
Justin Spahr-Summers 9e040347f5 Merge pull request #145 from Squirrel/xcode6.3
Upgrade dependencies for Xcode 6.3
2015-04-09 16:06:29 -07:00
Justin Spahr-Summers c99ce7dfbb Upgrade dependencies for Xcode 6.3 2015-04-09 14:59:58 -07:00
Justin Spahr-Summers 6b017d5d3f Fix call to superclass method that doesn't exist
Not sure how this ever worked.
2015-04-09 14:56:04 -07:00
Justin Spahr-Summers b1f4ecf615 Merge pull request #143 from Squirrel/quote-paths-when-signing
Quote the paths passed to otool.
2015-02-12 12:15:42 -08:00
Rob Rix fd29bc392f Quote the paths passed to otool.
We pass in a path which could contain spaces, so we need to quote it.
2015-02-12 10:02:32 -05:00
150 changed files with 8001 additions and 902 deletions
+10
View File
@@ -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
View File
@@ -1,3 +1,3 @@
github "jspahrsummers/xcconfigs" >= 0.7.1
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
View File
@@ -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.1"
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"
+41 -6
View File
@@ -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";
@@ -139,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.
@@ -148,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"
}
```
@@ -171,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
+86 -27
View File
@@ -13,8 +13,6 @@
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 */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
53710D7417D8F59700A992DE /* SQRLDeepCodesignSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 53710D7317D8F59700A992DE /* SQRLDeepCodesignSpec.m */; };
53710D8417D8F5CB00A992DE /* deep-codesign in Resources */ = {isa = PBXBuildFile; fileRef = 53710D7F17D8F5C300A992DE /* deep-codesign */; };
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 */; };
@@ -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 */; };
@@ -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 */; };
@@ -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 */; };
@@ -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 = 0620;
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,6 +1549,10 @@
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;
@@ -1535,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;
};
@@ -1544,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;
@@ -1552,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;
@@ -1560,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;
@@ -1568,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;
@@ -1586,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;
};
@@ -1604,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;
};
@@ -1612,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;
};
@@ -1621,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;
@@ -1629,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;
@@ -1647,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;
};
@@ -1655,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;
};
@@ -1664,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;
@@ -1672,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;
@@ -1690,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 = "0620"
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 = "0620"
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,15 +38,18 @@
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
runnableDebuggingMode = "0">
@@ -62,10 +65,10 @@
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0620"
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
+12 -2
View File
@@ -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
+48 -9
View File
@@ -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
+2 -2
View File
@@ -40,9 +40,9 @@
#pragma mark MTLJSONSerializing
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return [super.JSONKeyPathsByPropertyKey mtl_dictionaryByAddingEntriesFromDictionary:@{
return @{
@keypath(SQRLDownloadedUpdate.new, bundleURL): NSNull.null
}];
};
}
@end
+23 -4
View File
@@ -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"
@@ -402,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];
@@ -505,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);
@@ -531,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]];
}
+19 -4
View File
@@ -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
+1 -1
View File
@@ -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;
+60
View File
@@ -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)
+293 -98
View File
@@ -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 {
+2 -1
View File
@@ -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"];
+51 -16
View File
@@ -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);
@@ -116,19 +115,43 @@ static void installRequest(RACSignal *readRequestSignal, SQRLDirectoryManager *d
action = [[action
deliverOn:RACScheduler.mainThreadScheduler]
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);
}
}];
}
@@ -148,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;
+1 -1
View File
@@ -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>
+10
View File
@@ -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
+6 -41
View File
@@ -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
+47
View File
@@ -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"
}
}
]
}
+1 -1
View File
@@ -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", ^{
-126
View File
@@ -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
+1 -1
View File
@@ -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());
+3 -3
View File
@@ -88,7 +88,7 @@ it(@"should install an update to another volume", ^{
[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", ^{
@@ -133,7 +133,7 @@ describe(@"with backup restoration", ^{
[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());
@@ -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];
@@ -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];
+2 -2
View File
@@ -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
View File
@@ -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());
});
});
+1 -1
View File
@@ -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>
@@ -0,0 +1 @@
Versions/Current/Headers
@@ -0,0 +1 @@
Versions/Current/Mantle
@@ -0,0 +1 @@
Versions/Current/Modules
@@ -0,0 +1 @@
Versions/Current/Resources
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
@@ -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
@@ -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
@@ -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);
@@ -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
@@ -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
@@ -0,0 +1,6 @@
framework module Mantle {
umbrella header "Mantle.h"
export *
module * { export * }
}
@@ -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>
@@ -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>
@@ -0,0 +1 @@
Versions/Current/Headers
@@ -0,0 +1 @@
Versions/Current/Modules
@@ -0,0 +1 @@
Versions/Current/ReactiveCocoa
@@ -0,0 +1 @@
Versions/Current/Resources
@@ -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])
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,148 @@
//
// RACScheduler.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 4/16/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
/// The priority for the scheduler.
///
/// RACSchedulerPriorityHigh - High priority.
/// RACSchedulerPriorityDefault - Default priority.
/// RACSchedulerPriorityLow - Low priority.
/// RACSchedulerPriorityBackground - Background priority.
typedef enum : long {
RACSchedulerPriorityHigh = DISPATCH_QUEUE_PRIORITY_HIGH,
RACSchedulerPriorityDefault = DISPATCH_QUEUE_PRIORITY_DEFAULT,
RACSchedulerPriorityLow = DISPATCH_QUEUE_PRIORITY_LOW,
RACSchedulerPriorityBackground = DISPATCH_QUEUE_PRIORITY_BACKGROUND,
} RACSchedulerPriority;
/// Scheduled with -scheduleRecursiveBlock:, this type of block is passed a block
/// with which it can call itself recursively.
typedef void (^RACSchedulerRecursiveBlock)(void (^reschedule)(void));
@class RACDisposable;
/// Schedulers are used to control when and where work is performed.
@interface RACScheduler : NSObject
/// A singleton scheduler that immediately executes the blocks it is given.
///
/// **Note:** Unlike most other schedulers, this does not set the current
/// scheduler. There may still be a valid +currentScheduler if this is used
/// within a block scheduled on a different scheduler.
+ (RACScheduler *)immediateScheduler;
/// A singleton scheduler that executes blocks in the main thread.
+ (RACScheduler *)mainThreadScheduler;
/// Creates and returns a new background scheduler with the given priority and
/// name. The name is for debug and instrumentation purposes only.
///
/// Scheduler creation is cheap. It's unnecessary to save the result of this
/// method call unless you want to serialize some actions on the same background
/// scheduler.
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name;
/// Invokes +schedulerWithPriority:name: with a default name.
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority;
/// Invokes +schedulerWithPriority: with RACSchedulerPriorityDefault.
+ (RACScheduler *)scheduler;
/// The current scheduler. This will only be valid when used from within a
/// -[RACScheduler schedule:] block or when on the main thread.
+ (RACScheduler *)currentScheduler;
/// Schedule the given block for execution on the scheduler.
///
/// Scheduled blocks will be executed in the order in which they were scheduled.
///
/// block - The block to schedule for execution. Cannot be nil.
///
/// Returns a disposable which can be used to cancel the scheduled block before
/// it begins executing, or nil if cancellation is not supported.
- (RACDisposable *)schedule:(void (^)(void))block;
/// Schedule the given block for execution on the scheduler at or after
/// a specific time.
///
/// Note that blocks scheduled for a certain time will not preempt any other
/// scheduled work that is executing at the time.
///
/// When invoked on the +immediateScheduler, the calling thread **will block**
/// until the specified time.
///
/// date - The earliest time at which `block` should begin executing. The block
/// may not execute immediately at this time, whether due to system load
/// or another block on the scheduler currently being run. Cannot be nil.
/// block - The block to schedule for execution. Cannot be nil.
///
/// Returns a disposable which can be used to cancel the scheduled block before
/// it begins executing, or nil if cancellation is not supported.
- (RACDisposable *)after:(NSDate *)date schedule:(void (^)(void))block;
/// Schedule the given block for execution on the scheduler after the delay.
///
/// Converts the delay into an NSDate, then invokes `-after:schedule:`.
- (RACDisposable *)afterDelay:(NSTimeInterval)delay schedule:(void (^)(void))block;
/// Reschedule the given block at a particular interval, starting at a specific
/// time, and with a given leeway for deferral.
///
/// Note that blocks scheduled for a certain time will not preempt any other
/// scheduled work that is executing at the time.
///
/// Regardless of the value of `leeway`, the given block may not execute exactly
/// at `when` or exactly on successive intervals, whether due to system load or
/// because another block is currently being run on the scheduler.
///
/// It is considered undefined behavior to invoke this method on the
/// +immediateScheduler.
///
/// date - The earliest time at which `block` should begin executing. The
/// block may not execute immediately at this time, whether due to
/// system load or another block on the scheduler currently being
/// run. Cannot be nil.
/// interval - The interval at which the block should be rescheduled, starting
/// from `date`. This will use the system wall clock, to avoid
/// skew when the computer goes to sleep.
/// leeway - A hint to the system indicating the number of seconds that each
/// scheduling can be deferred. Note that this is just a hint, and
/// there may be some additional latency no matter what.
/// block - The block to repeatedly schedule for execution. Cannot be nil.
///
/// Returns a disposable which can be used to cancel the automatic scheduling and
/// rescheduling, or nil if cancellation is not supported.
- (RACDisposable *)after:(NSDate *)date repeatingEvery:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway schedule:(void (^)(void))block;
/// Schedule the given recursive block for execution on the scheduler. The
/// scheduler will automatically flatten any recursive scheduling into iteration
/// instead, so this can be used without issue for blocks that may keep invoking
/// themselves forever.
///
/// Scheduled blocks will be executed in the order in which they were scheduled.
///
/// recursiveBlock - The block to schedule for execution. When invoked, the
/// recursive block will be passed a `void (^)(void)` block
/// which will reschedule the recursive block at the end of the
/// receiver's queue. This passed-in block will automatically
/// skip scheduling if the scheduling of the `recursiveBlock`
/// was disposed in the meantime.
///
/// Returns a disposable which can be used to cancel the scheduled block before
/// it begins executing, or to stop it from rescheduling if it's already begun
/// execution.
- (RACDisposable *)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock;
@end
@interface RACScheduler (Deprecated)
+ (RACScheduler *)schedulerWithQueue:(dispatch_queue_t)queue name:(NSString *)name __attribute__((deprecated("Use -[RACTargetQueueScheduler initWithName:targetQueue:] instead.")));
@end
@@ -0,0 +1,18 @@
//
// RACScopedDisposable.h
// ReactiveCocoa
//
// Created by Josh Abernathy on 3/28/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACDisposable.h"
/// A disposable that calls its own -dispose when it is dealloc'd.
@interface RACScopedDisposable : RACDisposable
/// Creates a new scoped disposable that will also dispose of the given
/// disposable when it is dealloc'd.
+ (instancetype)scopedDisposableWithDisposable:(RACDisposable *)disposable;
@end
@@ -0,0 +1,154 @@
//
// RACSequence.h
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2012-10-29.
// Copyright (c) 2012 GitHub. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "RACStream.h"
@class RACScheduler;
@class RACSignal;
/// Represents an immutable sequence of values. Unless otherwise specified, the
/// sequences' values are evaluated lazily on demand. Like Cocoa collections,
/// sequences cannot contain nil.
///
/// Most inherited RACStream methods that accept a block will execute the block
/// _at most_ once for each value that is evaluated in the returned sequence.
/// Side effects are subject to the behavior described in
/// +sequenceWithHeadBlock:tailBlock:.
///
/// Implemented as a class cluster. A minimal implementation for a subclass
/// consists simply of -head and -tail.
@interface RACSequence : RACStream <NSCoding, NSCopying, NSFastEnumeration>
/// The first object in the sequence, or nil if the sequence is empty.
///
/// Subclasses must provide an implementation of this method.
@property (nonatomic, strong, readonly) id head;
/// All but the first object in the sequence, or nil if the sequence is empty.
///
/// Subclasses must provide an implementation of this method.
@property (nonatomic, strong, readonly) RACSequence *tail;
/// Evaluates the full sequence to produce an equivalently-sized array.
@property (nonatomic, copy, readonly) NSArray *array;
/// Returns an enumerator of all objects in the sequence.
@property (nonatomic, copy, readonly) NSEnumerator *objectEnumerator;
/// Converts a sequence into an eager sequence.
///
/// An eager sequence fully evaluates all of its values immediately. Sequences
/// derived from an eager sequence will also be eager.
///
/// Returns a new eager sequence, or the receiver if the sequence is already
/// eager.
@property (nonatomic, copy, readonly) RACSequence *eagerSequence;
/// Converts a sequence into a lazy sequence.
///
/// A lazy sequence evaluates its values on demand, as they are accessed.
/// Sequences derived from a lazy sequence will also be lazy.
///
/// Returns a new lazy sequence, or the receiver if the sequence is already lazy.
@property (nonatomic, copy, readonly) RACSequence *lazySequence;
/// Invokes -signalWithScheduler: with a new RACScheduler.
- (RACSignal *)signal;
/// Evaluates the full sequence on the given scheduler.
///
/// Each item is evaluated in its own scheduled block, such that control of the
/// scheduler is yielded between each value.
///
/// Returns a signal which sends the receiver's values on the given scheduler as
/// they're evaluated.
- (RACSignal *)signalWithScheduler:(RACScheduler *)scheduler;
/// Applies a left fold to the sequence.
///
/// This is the same as iterating the sequence along with a provided start value.
/// This uses a constant amount of memory. A left fold is left-associative so in
/// the sequence [1,2,3] the block would applied in the following order:
/// reduce(reduce(reduce(start, 1), 2), 3)
///
/// start - The starting value for the fold. Used as `accumulator` for the
/// first fold.
/// reduce - The block used to combine the accumulated value and the next value.
/// Cannot be nil.
///
/// Returns a reduced value.
- (id)foldLeftWithStart:(id)start reduce:(id (^)(id accumulator, id value))reduce;
/// Applies a right fold to the sequence.
///
/// A right fold is equivalent to recursion on the list. The block is evaluated
/// from the right to the left in list. It is right associative so it's applied
/// to the rightmost elements first. For example, in the sequence [1,2,3] the
/// block is applied in the order:
/// reduce(1, reduce(2, reduce(3, start)))
///
/// start - The starting value for the fold.
/// reduce - The block used to combine the accumulated value and the next head.
/// The block is given the accumulated value and the value of the rest
/// of the computation (result of the recursion). This is computed when
/// you retrieve its value using `rest.head`. This allows you to
/// prevent unnecessary computation by not accessing `rest.head` if you
/// don't need to.
///
/// Returns a reduced value.
- (id)foldRightWithStart:(id)start reduce:(id (^)(id first, RACSequence *rest))reduce;
/// Check if any value in sequence passes the block.
///
/// block - The block predicate used to check each item. Cannot be nil.
///
/// Returns a boolean indiciating if any value in the sequence passed.
- (BOOL)any:(BOOL (^)(id value))block;
/// Check if all values in the sequence pass the block.
///
/// block - The block predicate used to check each item. Cannot be nil.
///
/// Returns a boolean indicating if all values in the sequence passed.
- (BOOL)all:(BOOL (^)(id value))block;
/// Returns the first object that passes the block.
///
/// block - The block predicate used to check each item. Cannot be nil.
///
/// Returns an object that passes the block or nil if no objects passed.
- (id)objectPassingTest:(BOOL (^)(id value))block;
/// Creates a sequence that dynamically generates its values.
///
/// headBlock - Invoked the first time -head is accessed.
/// tailBlock - Invoked the first time -tail is accessed.
///
/// The results from each block are memoized, so each block will be invoked at
/// most once, no matter how many times the head and tail properties of the
/// sequence are accessed.
///
/// Any side effects in `headBlock` or `tailBlock` should be thread-safe, since
/// the sequence may be evaluated at any time from any thread. Not only that, but
/// -tail may be accessed before -head, or both may be accessed simultaneously.
/// As noted above, side effects will only be triggered the _first_ time -head or
/// -tail is invoked.
///
/// Returns a sequence that lazily invokes the given blocks to provide head and
/// tail. `headBlock` must not be nil.
+ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock;
@end
@interface RACSequence (Deprecated)
- (id)foldLeftWithStart:(id)start combine:(id (^)(id accumulator, id value))combine __attribute__((deprecated("Renamed to -foldLeftWithStart:reduce:")));
- (id)foldRightWithStart:(id)start combine:(id (^)(id first, RACSequence *rest))combine __attribute__((deprecated("Renamed to -foldRightWithStart:reduce:")));
@end
@@ -0,0 +1,43 @@
//
// RACSerialDisposable.h
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2013-07-22.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACDisposable.h"
/// A disposable that contains exactly one other disposable and allows it to be
/// swapped out atomically.
@interface RACSerialDisposable : RACDisposable
/// The inner disposable managed by the serial disposable.
///
/// This property is thread-safe for reading and writing. However, if you want to
/// read the current value _and_ write a new one atomically, use
/// -swapInDisposable: instead.
///
/// Disposing of the receiver will also dispose of the current disposable set for
/// this property, then set the property to nil. If any new disposable is set
/// after the receiver is disposed, it will be disposed immediately and this
/// property will remain set to nil.
@property (atomic, strong) RACDisposable *disposable;
/// Creates a serial disposable which will wrap the given disposable.
///
/// disposable - The value to set for `disposable`. This may be nil.
///
/// Returns a RACSerialDisposable, or nil if an error occurs.
+ (instancetype)serialDisposableWithDisposable:(RACDisposable *)disposable;
/// Atomically swaps the receiver's `disposable` for `newDisposable`.
///
/// newDisposable - The new value for `disposable`. If the receiver has already
/// been disposed, this disposable will be too, and `disposable`
/// will remain set to nil. This argument may be nil.
///
/// Returns the previous value for the `disposable` property.
- (RACDisposable *)swapInDisposable:(RACDisposable *)newDisposable;
@end
@@ -0,0 +1,709 @@
//
// RACSignal+Operations.h
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2012-09-06.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "RACSignal.h"
/// The domain for errors originating in RACSignal operations.
extern NSString * const RACSignalErrorDomain;
/// The error code used with -timeout:.
extern const NSInteger RACSignalErrorTimedOut;
/// The error code used when a value passed into +switch:cases:default: does not
/// match any of the cases, and no default was given.
extern const NSInteger RACSignalErrorNoMatchingCase;
@class RACCommand;
@class RACDisposable;
@class RACMulticastConnection;
@class RACScheduler;
@class RACSequence;
@class RACSubject;
@class RACTuple;
@protocol RACSubscriber;
@interface RACSignal (Operations)
/// Do the given block on `next`. This should be used to inject side effects into
/// the signal.
- (RACSignal *)doNext:(void (^)(id x))block;
/// Do the given block on `error`. This should be used to inject side effects
/// into the signal.
- (RACSignal *)doError:(void (^)(NSError *error))block;
/// Do the given block on `completed`. This should be used to inject side effects
/// into the signal.
- (RACSignal *)doCompleted:(void (^)(void))block;
/// Sends `next`s only if we don't receive another `next` in `interval` seconds.
///
/// If a `next` is received, and then another `next` is received before
/// `interval` seconds have passed, the first value is discarded.
///
/// After `interval` seconds have passed since the most recent `next` was sent,
/// the most recent `next` is forwarded on the scheduler that the value was
/// originally received on. If +[RACScheduler currentScheduler] was nil at the
/// time, a private background scheduler is used.
///
/// Returns a signal which sends throttled and delayed `next` events. Completion
/// and errors are always forwarded immediately.
- (RACSignal *)throttle:(NSTimeInterval)interval;
/// Throttles `next`s for which `predicate` returns YES.
///
/// When `predicate` returns YES for a `next`:
///
/// 1. If another `next` is received before `interval` seconds have passed, the
/// prior value is discarded. This happens regardless of whether the new
/// value will be throttled.
/// 2. After `interval` seconds have passed since the value was originally
/// received, it will be forwarded on the scheduler that it was received
/// upon. If +[RACScheduler currentScheduler] was nil at the time, a private
/// background scheduler is used.
///
/// When `predicate` returns NO for a `next`, it is forwarded immediately,
/// without any throttling.
///
/// interval - The number of seconds for which to buffer the latest value that
/// passes `predicate`.
/// predicate - Passed each `next` from the receiver, this block returns
/// whether the given value should be throttled. This argument must
/// not be nil.
///
/// Returns a signal which sends `next` events, throttled when `predicate`
/// returns YES. Completion and errors are always forwarded immediately.
- (RACSignal *)throttle:(NSTimeInterval)interval valuesPassingTest:(BOOL (^)(id next))predicate;
/// Forwards `next` and `completed` events after delaying for `interval` seconds
/// on the current scheduler (on which the events were delivered).
///
/// If +[RACScheduler currentScheduler] is nil when `next` or `completed` is
/// received, a private background scheduler is used.
///
/// Returns a signal which sends delayed `next` and `completed` events. Errors
/// are always forwarded immediately.
- (RACSignal *)delay:(NSTimeInterval)interval;
/// Resubscribes when the signal completes.
- (RACSignal *)repeat;
/// Executes the given block each time a subscription is created.
///
/// block - A block which defines the subscription side effects. Cannot be `nil`.
///
/// Example:
///
/// // Write new file, with backup.
/// [[[[fileManager
/// rac_createFileAtPath:path contents:data]
/// initially:^{
/// // 2. Second, backup current file
/// [fileManager moveItemAtPath:path toPath:backupPath error:nil];
/// }]
/// initially:^{
/// // 1. First, acquire write lock.
/// [writeLock lock];
/// }]
/// finally:^{
/// [writeLock unlock];
/// }];
///
/// Returns a signal that passes through all events of the receiver, plus
/// introduces side effects which occur prior to any subscription side effects
/// of the receiver.
- (RACSignal *)initially:(void (^)(void))block;
/// Executes the given block when the signal completes or errors.
- (RACSignal *)finally:(void (^)(void))block;
/// Divides the receiver's `next`s into buffers which deliver every `interval`
/// seconds.
///
/// interval - The interval in which values are grouped into one buffer.
/// scheduler - The scheduler upon which the returned signal will deliver its
/// values. This must not be nil or +[RACScheduler
/// immediateScheduler].
///
/// Returns a signal which sends RACTuples of the buffered values at each
/// interval on `scheduler`. When the receiver completes, any currently-buffered
/// values will be sent immediately.
- (RACSignal *)bufferWithTime:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler;
/// Collects all receiver's `next`s into a NSArray. Nil values will be converted
/// to NSNull.
///
/// This corresponds to the `ToArray` method in Rx.
///
/// Returns a signal which sends a single NSArray when the receiver completes
/// successfully.
- (RACSignal *)collect;
/// Takes the last `count` `next`s after the receiving signal completes.
- (RACSignal *)takeLast:(NSUInteger)count;
/// Combines the latest values from the receiver and the given signal into
/// RACTuples, once both have sent at least one `next`.
///
/// Any additional `next`s will result in a new RACTuple with the latest values
/// from both signals.
///
/// signal - The signal to combine with. This argument must not be nil.
///
/// Returns a signal which sends RACTuples of the combined values, forwards any
/// `error` events, and completes when both input signals complete.
- (RACSignal *)combineLatestWith:(RACSignal *)signal;
/// Combines the latest values from the given signals into RACTuples, once all
/// the signals have sent at least one `next`.
///
/// Any additional `next`s will result in a new RACTuple with the latest values
/// from all signals.
///
/// signals - The signals to combine. If this collection is empty, the returned
/// signal will immediately complete upon subscription.
///
/// Returns a signal which sends RACTuples of the combined values, forwards any
/// `error` events, and completes when all input signals complete.
+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals;
/// Combines signals using +combineLatest:, then reduces the resulting tuples
/// into a single value using -reduceEach:.
///
/// signals - The signals to combine. If this collection is empty, the
/// returned signal will immediately complete upon subscription.
/// reduceBlock - The block which reduces the latest values from all the
/// signals into one value. It must take as many arguments as the
/// number of signals given. Each argument will be an object
/// argument. The return value must be an object. This argument
/// must not be nil.
///
/// Example:
///
/// [RACSignal combineLatest:@[ stringSignal, intSignal ] reduce:^(NSString *string, NSNumber *number) {
/// return [NSString stringWithFormat:@"%@: %@", string, number];
/// }];
///
/// Returns a signal which sends the results from each invocation of
/// `reduceBlock`.
+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock;
/// Merges the receiver and the given signal with `+merge:` and returns the
/// resulting signal.
- (RACSignal *)merge:(RACSignal *)signal;
/// Sends the latest `next` from any of the signals.
///
/// Returns a signal that passes through values from each of the given signals,
/// and sends `completed` when all of them complete. If any signal sends an error,
/// the returned signal sends `error` immediately.
+ (RACSignal *)merge:(id<NSFastEnumeration>)signals;
/// Merges the signals sent by the receiver into a flattened signal, but only
/// subscribes to `maxConcurrent` number of signals at a time. New signals are
/// queued and subscribed to as other signals complete.
///
/// If an error occurs on any of the signals, it is sent on the returned signal.
/// It completes only after the receiver and all sent signals have completed.
///
/// This corresponds to `Merge<TSource>(IObservable<IObservable<TSource>>, Int32)`
/// in Rx.
///
/// maxConcurrent - the maximum number of signals to subscribe to at a
/// time. If 0, it subscribes to an unlimited number of
/// signals.
- (RACSignal *)flatten:(NSUInteger)maxConcurrent;
/// Ignores all `next`s from the receiver, waits for the receiver to complete,
/// then subscribes to a new signal.
///
/// block - A block which will create or obtain a new signal to subscribe to,
/// executed only after the receiver completes. This block must not be
/// nil, and it must not return a nil signal.
///
/// Returns a signal which will pass through the events of the signal created in
/// `block`. If the receiver errors out, the returned signal will error as well.
- (RACSignal *)then:(RACSignal * (^)(void))block;
/// Concats the inner signals of a signal of signals.
- (RACSignal *)concat;
/// Aggregates the `next` values of the receiver into a single combined value.
///
/// The algorithm proceeds as follows:
///
/// 1. `start` is passed into the block as the `running` value, and the first
/// element of the receiver is passed into the block as the `next` value.
/// 2. The result of the invocation (`running`) and the next element of the
/// receiver (`next`) is passed into `reduceBlock`.
/// 3. Steps 2 and 3 are repeated until all values have been processed.
/// 4. The last result of `reduceBlock` is sent on the returned signal.
///
/// This method is similar to -scanWithStart:reduce:, except that only the
/// final result is sent on the returned signal.
///
/// start - The value to be combined with the first element of the
/// receiver. This value may be `nil`.
/// reduceBlock - The block that describes how to combine values of the
/// receiver. If the receiver is empty, this block will never be
/// invoked. Cannot be nil.
///
/// Returns a signal that will send the aggregated value when the receiver
/// completes, then itself complete. If the receiver never sends any values,
/// `start` will be sent instead.
- (RACSignal *)aggregateWithStart:(id)start reduce:(id (^)(id running, id next))reduceBlock;
/// Aggregates the `next` values of the receiver into a single combined value.
/// This is indexed version of -aggregateWithStart:reduce:.
///
/// start - The value to be combined with the first element of the
/// receiver. This value may be `nil`.
/// reduceBlock - The block that describes how to combine values of the
/// receiver. This block takes zero-based index value as the last
/// parameter. If the receiver is empty, this block will never be
/// invoked. Cannot be nil.
///
/// Returns a signal that will send the aggregated value when the receiver
/// completes, then itself complete. If the receiver never sends any values,
/// `start` will be sent instead.
- (RACSignal *)aggregateWithStart:(id)start reduceWithIndex:(id (^)(id running, id next, NSUInteger index))reduceBlock;
/// Aggregates the `next` values of the receiver into a single combined value.
///
/// This invokes `startFactory` block on each subscription, then calls
/// -aggregateWithStart:reduce: with the return value of the block as start value.
///
/// startFactory - The block that returns start value which will be combined
/// with the first element of the receiver. Cannot be nil.
/// reduceBlock - The block that describes how to combine values of the
/// receiver. If the receiver is empty, this block will never be
/// invoked. Cannot be nil.
///
/// Returns a signal that will send the aggregated value when the receiver
/// completes, then itself complete. If the receiver never sends any values,
/// the return value of `startFactory` will be sent instead.
- (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory reduce:(id (^)(id running, id next))reduceBlock;
/// Invokes -setKeyPath:onObject:nilValue: with `nil` for the nil value.
///
/// WARNING: Under certain conditions, this method is known to be thread-unsafe.
/// See the description in -setKeyPath:onObject:nilValue:.
- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object;
/// Binds the receiver to an object, automatically setting the given key path on
/// every `next`. When the signal completes, the binding is automatically
/// disposed of.
///
/// WARNING: Under certain conditions, this method is known to be thread-unsafe.
/// A crash can result if `object` is deallocated concurrently on
/// another thread within a window of time between a value being sent
/// on this signal and immediately prior to the invocation of
/// -setValue:forKeyPath:, which sets the property. To prevent this,
/// ensure `object` is deallocated on the same thread the receiver
/// sends on, or ensure that the returned disposable is disposed of
/// before `object` deallocates.
/// See https://github.com/ReactiveCocoa/ReactiveCocoa/pull/1184
///
/// Sending an error on the signal is considered undefined behavior, and will
/// generate an assertion failure in Debug builds.
///
/// A given key on an object should only have one active signal bound to it at any
/// given time. Binding more than one signal to the same property is considered
/// undefined behavior.
///
/// keyPath - The key path to update with `next`s from the receiver.
/// object - The object that `keyPath` is relative to.
/// nilValue - The value to set at the key path whenever `nil` is sent by the
/// receiver. This may be nil when binding to 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`).
///
/// Returns a disposable which can be used to terminate the binding.
- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object nilValue:(id)nilValue;
/// Sends NSDate.date every `interval` seconds.
///
/// interval - The time interval in seconds at which the current time is sent.
/// scheduler - The scheduler upon which the current NSDate should be sent. This
/// must not be nil or +[RACScheduler immediateScheduler].
///
/// Returns a signal that sends the current date/time every `interval` on
/// `scheduler`.
+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler;
/// Sends NSDate.date at intervals of at least `interval` seconds, up to
/// approximately `interval` + `leeway` seconds.
///
/// The created signal will defer sending each `next` for at least `interval`
/// seconds, and for an additional amount of time up to `leeway` seconds in the
/// interest of performance or power consumption. Note that some additional
/// latency is to be expected, even when specifying a `leeway` of 0.
///
/// interval - The base interval between `next`s.
/// scheduler - The scheduler upon which the current NSDate should be sent. This
/// must not be nil or +[RACScheduler immediateScheduler].
/// leeway - The maximum amount of additional time the `next` can be deferred.
///
/// Returns a signal that sends the current date/time at intervals of at least
/// `interval seconds` up to approximately `interval` + `leeway` seconds on
/// `scheduler`.
+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler withLeeway:(NSTimeInterval)leeway;
/// Takes `next`s until the `signalTrigger` sends `next` or `completed`.
///
/// Returns a signal which passes through all events from the receiver until
/// `signalTrigger` sends `next` or `completed`, at which point the returned signal
/// will send `completed`.
- (RACSignal *)takeUntil:(RACSignal *)signalTrigger;
/// Takes `next`s until the `replacement` sends an event.
///
/// replacement - The signal which replaces the receiver as soon as it sends an
/// event.
///
/// Returns a signal which passes through `next`s and `error` from the receiver
/// until `replacement` sends an event, at which point the returned signal will
/// send that event and switch to passing through events from `replacement`
/// instead, regardless of whether the receiver has sent events already.
- (RACSignal *)takeUntilReplacement:(RACSignal *)replacement;
/// Subscribes to the returned signal when an error occurs.
- (RACSignal *)catch:(RACSignal * (^)(NSError *error))catchBlock;
/// Subscribes to the given signal when an error occurs.
- (RACSignal *)catchTo:(RACSignal *)signal;
/// Runs `tryBlock` against each of the receiver's values, passing values
/// until `tryBlock` returns NO, or the receiver completes.
///
/// tryBlock - An action to run against each of the receiver's values.
/// The block should return YES to indicate that the action was
/// successful. This block must not be nil.
///
/// Example:
///
/// // The returned signal will send an error if data values cannot be
/// // written to `someFileURL`.
/// [signal try:^(NSData *data, NSError **errorPtr) {
/// return [data writeToURL:someFileURL options:NSDataWritingAtomic error:errorPtr];
/// }];
///
/// Returns a signal which passes through all the values of the receiver. If
/// `tryBlock` fails for any value, the returned signal will error using the
/// `NSError` passed out from the block.
- (RACSignal *)try:(BOOL (^)(id value, NSError **errorPtr))tryBlock;
/// Runs `mapBlock` against each of the receiver's values, mapping values until
/// `mapBlock` returns nil, or the receiver completes.
///
/// mapBlock - An action to map each of the receiver's values. The block should
/// return a non-nil value to indicate that the action was successful.
/// This block must not be nil.
///
/// Example:
///
/// // The returned signal will send an error if data cannot be read from
/// // `fileURL`.
/// [signal tryMap:^(NSURL *fileURL, NSError **errorPtr) {
/// return [NSData dataWithContentsOfURL:fileURL options:0 error:errorPtr];
/// }];
///
/// Returns a signal which transforms all the values of the receiver. If
/// `mapBlock` returns nil for any value, the returned signal will error using
/// the `NSError` passed out from the block.
- (RACSignal *)tryMap:(id (^)(id value, NSError **errorPtr))mapBlock;
/// Returns the first `next`. Note that this is a blocking call.
- (id)first;
/// Returns the first `next` or `defaultValue` if the signal completes or errors
/// without sending a `next`. Note that this is a blocking call.
- (id)firstOrDefault:(id)defaultValue;
/// Returns the first `next` or `defaultValue` if the signal completes or errors
/// without sending a `next`. If an error occurs success will be NO and error
/// will be populated. Note that this is a blocking call.
///
/// Both success and error may be NULL.
- (id)firstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error;
/// Blocks the caller and waits for the signal to complete.
///
/// error - If not NULL, set to any error that occurs.
///
/// Returns whether the signal completed successfully. If NO, `error` will be set
/// to the error that occurred.
- (BOOL)waitUntilCompleted:(NSError **)error;
/// Defers creation of a signal until the signal's actually subscribed to.
///
/// This can be used to effectively turn a hot signal into a cold signal.
+ (RACSignal *)defer:(RACSignal * (^)(void))block;
/// Every time the receiver sends a new RACSignal, subscribes and sends `next`s and
/// `error`s only for that signal.
///
/// The receiver must be a signal of signals.
///
/// Returns a signal which passes through `next`s and `error`s from the latest
/// signal sent by the receiver, and sends `completed` when both the receiver and
/// the last sent signal complete.
- (RACSignal *)switchToLatest;
/// Switches between the signals in `cases` as well as `defaultSignal` based on
/// the latest value sent by `signal`.
///
/// signal - A signal of objects used as keys in the `cases` dictionary.
/// This argument must not be nil.
/// cases - A dictionary that has signals as values. This argument must
/// not be nil. A RACTupleNil key in this dictionary will match
/// nil `next` events that are received on `signal`.
/// defaultSignal - The signal to pass through after `signal` sends a value for
/// which `cases` does not contain a signal. If nil, any
/// unmatched values will result in
/// a RACSignalErrorNoMatchingCase error.
///
/// Returns a signal which passes through `next`s and `error`s from one of the
/// the signals in `cases` or `defaultSignal`, and sends `completed` when both
/// `signal` and the last used signal complete. If no `defaultSignal` is given,
/// an unmatched `next` will result in an error on the returned signal.
+ (RACSignal *)switch:(RACSignal *)signal cases:(NSDictionary *)cases default:(RACSignal *)defaultSignal;
/// Switches between `trueSignal` and `falseSignal` based on the latest value
/// sent by `boolSignal`.
///
/// boolSignal - A signal of BOOLs determining whether `trueSignal` or
/// `falseSignal` should be active. This argument must not be nil.
/// trueSignal - The signal to pass through after `boolSignal` has sent YES.
/// This argument must not be nil.
/// falseSignal - The signal to pass through after `boolSignal` has sent NO. This
/// argument must not be nil.
///
/// Returns a signal which passes through `next`s and `error`s from `trueSignal`
/// and/or `falseSignal`, and sends `completed` when both `boolSignal` and the
/// last switched signal complete.
+ (RACSignal *)if:(RACSignal *)boolSignal then:(RACSignal *)trueSignal else:(RACSignal *)falseSignal;
/// Adds every `next` to an array. Nils are represented by NSNulls. Note that
/// this is a blocking call.
///
/// **This is not the same as the `ToArray` method in Rx.** See -collect for
/// that behavior instead.
///
/// Returns the array of `next` values, or nil if an error occurs.
- (NSArray *)toArray;
/// Adds every `next` to a sequence. Nils are represented by NSNulls.
///
/// This corresponds to the `ToEnumerable` method in Rx.
///
/// Returns a sequence which provides values from the signal as they're sent.
/// Trying to retrieve a value from the sequence which has not yet been sent will
/// block.
@property (nonatomic, strong, readonly) RACSequence *sequence;
/// Creates and returns a multicast connection. This allows you to share a single
/// subscription to the underlying signal.
- (RACMulticastConnection *)publish;
/// Creates and returns a multicast connection that pushes values into the given
/// subject. This allows you to share a single subscription to the underlying
/// signal.
- (RACMulticastConnection *)multicast:(RACSubject *)subject;
/// Multicasts the signal to a RACReplaySubject of unlimited capacity, and
/// immediately connects to the resulting RACMulticastConnection.
///
/// Returns the connected, multicasted signal.
- (RACSignal *)replay;
/// Multicasts the signal to a RACReplaySubject of capacity 1, and immediately
/// connects to the resulting RACMulticastConnection.
///
/// Returns the connected, multicasted signal.
- (RACSignal *)replayLast;
/// Multicasts the signal to a RACReplaySubject of unlimited capacity, and
/// lazily connects to the resulting RACMulticastConnection.
///
/// This means the returned signal will subscribe to the multicasted signal only
/// when the former receives its first subscription.
///
/// Returns the lazily connected, multicasted signal.
- (RACSignal *)replayLazily;
/// Sends an error after `interval` seconds if the source doesn't complete
/// before then.
///
/// The error will be in the RACSignalErrorDomain and have a code of
/// RACSignalErrorTimedOut.
///
/// interval - The number of seconds after which the signal should error out.
/// scheduler - The scheduler upon which any timeout error should be sent. This
/// must not be nil or +[RACScheduler immediateScheduler].
///
/// Returns a signal that passes through the receiver's events, until the stream
/// finishes or times out, at which point an error will be sent on `scheduler`.
- (RACSignal *)timeout:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler;
/// Creates and returns a signal that delivers its events on the given scheduler.
/// Any side effects of the receiver will still be performed on the original
/// thread.
///
/// This is ideal when the signal already performs its work on the desired
/// thread, but you want to handle its events elsewhere.
///
/// This corresponds to the `ObserveOn` method in Rx.
- (RACSignal *)deliverOn:(RACScheduler *)scheduler;
/// Creates and returns a signal that executes its side effects and delivers its
/// events on the given scheduler.
///
/// Use of this operator should be avoided whenever possible, because the
/// receiver's side effects may not be safe to run on another thread. If you just
/// want to receive the signal's events on `scheduler`, use -deliverOn: instead.
- (RACSignal *)subscribeOn:(RACScheduler *)scheduler;
/// Creates and returns a signal that delivers its events on the main thread.
/// If events are already being sent on the main thread, they may be passed on
/// without delay. An event will instead be queued for later delivery on the main
/// thread if sent on another thread, or if a previous event is already being
/// processed, or has been queued.
///
/// Any side effects of the receiver will still be performed on the original
/// thread.
///
/// This can be used when a signal will cause UI updates, to avoid potential
/// flicker caused by delayed delivery of events, such as the first event from
/// a RACObserve at view instantiation.
- (RACSignal *)deliverOnMainThread;
/// Groups each received object into a group, as determined by calling `keyBlock`
/// with that object. The object sent is transformed by calling `transformBlock`
/// with the object. If `transformBlock` is nil, it sends the original object.
///
/// The returned signal is a signal of RACGroupedSignal.
- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock transform:(id (^)(id object))transformBlock;
/// Calls -[RACSignal groupBy:keyBlock transform:nil].
- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock;
/// Sends an [NSNumber numberWithBool:YES] if the receiving signal sends any
/// objects.
- (RACSignal *)any;
/// Sends an [NSNumber numberWithBool:YES] if the receiving signal sends any
/// objects that pass `predicateBlock`.
///
/// predicateBlock - cannot be nil.
- (RACSignal *)any:(BOOL (^)(id object))predicateBlock;
/// Sends an [NSNumber numberWithBool:YES] if all the objects the receiving
/// signal sends pass `predicateBlock`.
///
/// predicateBlock - cannot be nil.
- (RACSignal *)all:(BOOL (^)(id object))predicateBlock;
/// Resubscribes to the receiving signal if an error occurs, up until it has
/// retried the given number of times.
///
/// retryCount - if 0, it keeps retrying until it completes.
- (RACSignal *)retry:(NSInteger)retryCount;
/// Resubscribes to the receiving signal if an error occurs.
- (RACSignal *)retry;
/// Sends the latest value from the receiver only when `sampler` sends a value.
/// The returned signal could repeat values if `sampler` fires more often than
/// the receiver. Values from `sampler` are ignored before the receiver sends
/// its first value.
///
/// sampler - The signal that controls when the latest value from the receiver
/// is sent. Cannot be nil.
- (RACSignal *)sample:(RACSignal *)sampler;
/// Ignores all `next`s from the receiver.
///
/// Returns a signal which only passes through `error` or `completed` events from
/// the receiver.
- (RACSignal *)ignoreValues;
/// Converts each of the receiver's events into a RACEvent object.
///
/// Returns a signal which sends the receiver's events as RACEvents, and
/// completes after the receiver sends `completed` or `error`.
- (RACSignal *)materialize;
/// Converts each RACEvent in the receiver back into "real" RACSignal events.
///
/// Returns a signal which sends `next` for each value RACEvent, `error` for each
/// error RACEvent, and `completed` for each completed RACEvent.
- (RACSignal *)dematerialize;
/// Inverts each NSNumber-wrapped BOOL sent by the receiver. It will assert if
/// the receiver sends anything other than NSNumbers.
///
/// Returns a signal of inverted NSNumber-wrapped BOOLs.
- (RACSignal *)not;
/// Performs a boolean AND on all of the RACTuple of NSNumbers in sent by the receiver.
///
/// Asserts if the receiver sends anything other than a RACTuple of one or more NSNumbers.
///
/// Returns a signal that applies AND to each NSNumber in the tuple.
- (RACSignal *)and;
/// Performs a boolean OR on all of the RACTuple of NSNumbers in sent by the receiver.
///
/// Asserts if the receiver sends anything other than a RACTuple of one or more NSNumbers.
///
/// Returns a signal that applies OR to each NSNumber in the tuple.
- (RACSignal *)or;
/// Sends the result of calling the block with arguments as packed in each RACTuple
/// sent by the receiver.
///
/// The receiver must send tuple values, where the first element of the tuple is
/// a block, taking a number of parameters equal to the count of the remaining
/// elements of the tuple, and returning an object. Each block must take at least
/// one argument, so each tuple must contain at least 2 elements.
///
/// Example:
///
/// RACSignal *adder = [RACSignal return:^(NSNumber *a, NSNumber *b) {
/// return @(a.intValue + b.intValue);
/// }];
/// RACSignal *sums = [[RACSignal
/// combineLatest:@[ adder, as, bs ]]
/// reduceApply];
///
/// Returns a signal of the result of applying the first element of each tuple
/// to the remaining elements.
- (RACSignal *)reduceApply;
@end
@interface RACSignal (OperationsDeprecated)
- (RACSignal *)windowWithStart:(RACSignal *)openSignal close:(RACSignal * (^)(RACSignal *start))closeBlock __attribute__((deprecated("See https://github.com/ReactiveCocoa/ReactiveCocoa/issues/587")));
- (RACSignal *)buffer:(NSUInteger)bufferCount __attribute__((deprecated("See https://github.com/ReactiveCocoa/ReactiveCocoa/issues/587")));
- (RACSignal *)let:(RACSignal * (^)(RACSignal *sharedSignal))letBlock __attribute__((deprecated("Use -publish instead")));
+ (RACSignal *)interval:(NSTimeInterval)interval __attribute__((deprecated("Use +interval:onScheduler: instead")));
+ (RACSignal *)interval:(NSTimeInterval)interval withLeeway:(NSTimeInterval)leeway __attribute__((deprecated("Use +interval:onScheduler:withLeeway: instead")));
- (RACSignal *)bufferWithTime:(NSTimeInterval)interval __attribute__((deprecated("Use -bufferWithTime:onScheduler: instead")));
- (RACSignal *)timeout:(NSTimeInterval)interval __attribute__((deprecated("Use -timeout:onScheduler: instead")));
- (RACDisposable *)toProperty:(NSString *)keyPath onObject:(NSObject *)object __attribute__((deprecated("Renamed to -setKeyPath:onObject:")));
- (RACSignal *)ignoreElements __attribute__((deprecated("Renamed to -ignoreValues")));
- (RACSignal *)sequenceNext:(RACSignal * (^)(void))block __attribute__((deprecated("Renamed to -then:")));
- (RACSignal *)aggregateWithStart:(id)start combine:(id (^)(id running, id next))combineBlock __attribute__((deprecated("Renamed to -aggregateWithStart:reduce:")));
- (RACSignal *)aggregateWithStartFactory:(id (^)(void))startFactory combine:(id (^)(id running, id next))combineBlock __attribute__((deprecated("Renamed to -aggregateWithStartFactory:reduce:")));
- (RACDisposable *)executeCommand:(RACCommand *)command __attribute__((deprecated("Use -flattenMap: or -subscribeNext: instead")));
@end

Some files were not shown because too many files have changed in this diff Show More