motion-wiretap 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md
CHANGED
@@ -1,16 +1,156 @@
|
|
1
|
-
|
1
|
+
Wiretap
|
2
2
|
-------
|
3
3
|
|
4
4
|
An iOS / OS X wrapper heavily inspired by ReactiveCocoa.
|
5
5
|
|
6
|
-
gem
|
6
|
+
gem install motion-wiretap
|
7
7
|
|
8
|
-
|
8
|
+
Run the OSX specs using `rake spec platform=osx`
|
9
|
+
|
10
|
+
First things first
|
11
|
+
------------------
|
12
|
+
|
13
|
+
You **MUST** call `cancel!` on any wiretaps you create, otherwise they will live
|
14
|
+
in memory forever. The upside is that you can create
|
9
15
|
|
10
16
|
Showdown
|
11
|
-
|
17
|
+
--------
|
12
18
|
|
13
19
|
Open these side by side to see the comparison:
|
14
20
|
|
15
21
|
[reactive.rb](https://gist.github.com/colinta/d0a273f8d858a8f61c73)
|
16
22
|
[reactive.mm](https://gist.github.com/colinta/5cfa588fed7b929193ae)
|
23
|
+
|
24
|
+
Usage
|
25
|
+
-----
|
26
|
+
|
27
|
+
These are taken from the specs.
|
28
|
+
|
29
|
+
### Key-Value Observation
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class Person
|
33
|
+
attr_accessor :name
|
34
|
+
attr_accessor :email
|
35
|
+
attr_accessor :address
|
36
|
+
end
|
37
|
+
|
38
|
+
# listen for changes
|
39
|
+
person = Person.new
|
40
|
+
person.wiretap(:name) do |name|
|
41
|
+
puts "name is now #{name}"
|
42
|
+
end
|
43
|
+
person.name = 'Joe'
|
44
|
+
# puts => "name is now Joe"
|
45
|
+
|
46
|
+
# bind the property of one object to the value of another
|
47
|
+
person_1 = Person.new
|
48
|
+
person_2 = Person.new
|
49
|
+
wiretap = person_1.wiretap(:name).bind_to(person_2.wiretap(:name))
|
50
|
+
person_2.name = 'Bob'
|
51
|
+
person_1.name # => "Bob"
|
52
|
+
|
53
|
+
# cancel a wiretap
|
54
|
+
wiretap.cancel!
|
55
|
+
person_2.name = 'Jane'
|
56
|
+
person_1.name
|
57
|
+
# => "BOB"
|
58
|
+
|
59
|
+
# bind the property of one object to the value of another, but change it using `map`
|
60
|
+
wiretap = person_1.wiretap(:name).bind_to(person_2.wiretap(:name).map { |value| value.upcase })
|
61
|
+
person_2.name = 'Bob'
|
62
|
+
person_1.name # => "BOB"
|
63
|
+
```
|
64
|
+
|
65
|
+
### Working with arrays
|
66
|
+
|
67
|
+
```
|
68
|
+
# combine the values `name` and `email`
|
69
|
+
person = Person.new
|
70
|
+
[
|
71
|
+
person.wiretap(:name),
|
72
|
+
person.wiretap(:email),
|
73
|
+
].wiretap.combine do |name, email|
|
74
|
+
puts "#{name} <#{email}>"
|
75
|
+
end
|
76
|
+
|
77
|
+
person.name = 'Kazuo'
|
78
|
+
# puts => "Kazuo <>"
|
79
|
+
person.email = 'kaz@example.com'
|
80
|
+
# puts => "Kazuo <kaz@example.com>"
|
81
|
+
|
82
|
+
# reduce/inject
|
83
|
+
person_1 = Person.new
|
84
|
+
person_2 = Person.new
|
85
|
+
[
|
86
|
+
person_1.wiretap(:name),
|
87
|
+
person_2.wiretap(:name),
|
88
|
+
].wiretap.reduce do |memo, name|
|
89
|
+
memo ||= []
|
90
|
+
memo + [name]
|
91
|
+
end
|
92
|
+
person_1.name = 'Mr. White'
|
93
|
+
person_1.name = 'Mr. Blue'
|
94
|
+
# => ['Mr. White', 'Mr. Blue']
|
95
|
+
|
96
|
+
# you can provide an initial 'memo' (default: nil)
|
97
|
+
[
|
98
|
+
person_1.wiretap(:name),
|
99
|
+
person_2.wiretap(:name),
|
100
|
+
].wiretap.reduce([]) do |memo, name|
|
101
|
+
memo + [name] # you should not change memo in place, the same one will be used on every change event
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
105
|
+
### Monitoring jobs
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
# monitor for background job completion
|
109
|
+
-> {
|
110
|
+
this_will_take_forever!
|
111
|
+
}.wiretap(Dispatch::Queue.concurrent) do
|
112
|
+
puts "done!"
|
113
|
+
end
|
114
|
+
|
115
|
+
# Note: this is convenient shorthand for calling `queue`, `and_then`, and `start`
|
116
|
+
-> {}.wiretap.queue(...).and_then do ... end.start
|
117
|
+
|
118
|
+
# send a stream of values. a lambda is passed in that will forward change
|
119
|
+
# events to the `listen` block
|
120
|
+
-> (on_change) do
|
121
|
+
5.times do |count|
|
122
|
+
on_change.call count
|
123
|
+
end
|
124
|
+
end.wiretap.queue(Dispatch::Queue.concurrent).listen do |index|
|
125
|
+
puts "...#{index}"
|
126
|
+
end.and_then do
|
127
|
+
puts "done!"
|
128
|
+
end.start
|
129
|
+
# => puts "...0", "...1", "...2", "...3", "...4", "done!"
|
130
|
+
```
|
131
|
+
|
132
|
+
### Let's do something practical!
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
# bind the `enabled` property to the Wiretap object to a check on whether
|
136
|
+
# `username_field.text` and `password_field.text` are blank
|
137
|
+
@login_button.wiretap(:enabled).bind_to(
|
138
|
+
[
|
139
|
+
@username_field.wiretap(:text),
|
140
|
+
@password_field.wiretap(:text),
|
141
|
+
].wiretap.combine do |username, password|
|
142
|
+
username && username.length > 0 && password && password.length > 0
|
143
|
+
end
|
144
|
+
)
|
145
|
+
```
|
146
|
+
|
147
|
+
### Notifications
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
notification = "NotificationName".wiretap do
|
151
|
+
puts "notification received!"
|
152
|
+
end
|
153
|
+
|
154
|
+
# rememder: it's *important* to cancel all wiretaps!
|
155
|
+
notification.cancel!
|
156
|
+
```
|
@@ -1,11 +1,10 @@
|
|
1
|
-
$motion_wiretaps = []
|
2
1
|
|
3
2
|
module MotionWiretap
|
4
3
|
|
5
4
|
class Wiretap
|
6
5
|
|
7
6
|
def initialize(&block)
|
8
|
-
|
7
|
+
# MotionWiretap.wiretaps << self # signal will be removed when it is completed
|
9
8
|
|
10
9
|
@is_torn_down = false
|
11
10
|
@is_completed = false
|
@@ -19,16 +18,16 @@ module MotionWiretap
|
|
19
18
|
listen &block if block
|
20
19
|
end
|
21
20
|
|
22
|
-
|
21
|
+
# this is the preferred way to turn off a wiretap; child classes override
|
22
|
+
# `teardown`, which is only ever called once.
|
23
|
+
def cancel!
|
24
|
+
return if @is_torn_down
|
25
|
+
@is_torn_down = true
|
23
26
|
teardown
|
24
|
-
super
|
25
27
|
end
|
26
28
|
|
29
|
+
# for internal use
|
27
30
|
def teardown
|
28
|
-
return if @is_torn_down
|
29
|
-
|
30
|
-
@is_torn_down = true
|
31
|
-
$motion_wiretaps.remove(self)
|
32
31
|
end
|
33
32
|
|
34
33
|
# specify the GCD queue that the listeners should be run on
|
@@ -97,7 +96,7 @@ module MotionWiretap
|
|
97
96
|
trigger_completed_on(block_or_wiretap)
|
98
97
|
end
|
99
98
|
|
100
|
-
|
99
|
+
cancel!
|
101
100
|
return self
|
102
101
|
end
|
103
102
|
|
@@ -131,7 +130,7 @@ module MotionWiretap
|
|
131
130
|
trigger_error_on(block_or_wiretap, error)
|
132
131
|
end
|
133
132
|
|
134
|
-
|
133
|
+
cancel!
|
135
134
|
return self
|
136
135
|
end
|
137
136
|
|
@@ -242,18 +241,18 @@ module MotionWiretap
|
|
242
241
|
|
243
242
|
end
|
244
243
|
|
245
|
-
class WiretapArray <
|
244
|
+
class WiretapArray < Wiretap
|
246
245
|
attr :targets
|
247
246
|
|
248
247
|
def initialize(targets, &block)
|
249
248
|
raise "Not only is listening to an empty array pointless, it will also cause errors" if targets.length == 0
|
250
249
|
|
251
|
-
# the complete trigger isn't called until all the
|
250
|
+
# the complete trigger isn't called until all the wiretap are complete
|
252
251
|
@uncompleted = targets.length
|
253
252
|
|
254
253
|
# targets can be an array of Wiretap objects (they will be monitored), or
|
255
254
|
# plain objects (they'll just be included in the sequence)
|
256
|
-
super(
|
255
|
+
super(&block)
|
257
256
|
|
258
257
|
# gets assigned to the wiretap value if it's a Wiretap, or the object
|
259
258
|
# itself if it is anything else.
|
@@ -293,9 +292,16 @@ module MotionWiretap
|
|
293
292
|
end
|
294
293
|
end
|
295
294
|
|
296
|
-
def
|
295
|
+
def cancel!
|
296
|
+
@wiretaps.each do |wiretap,index|
|
297
|
+
wiretap.cancel!
|
298
|
+
end
|
297
299
|
super
|
300
|
+
end
|
301
|
+
|
302
|
+
def teardown
|
298
303
|
@wiretaps = nil
|
304
|
+
super
|
299
305
|
end
|
300
306
|
|
301
307
|
def trigger_changed(*values)
|
@@ -325,8 +331,8 @@ module MotionWiretap
|
|
325
331
|
end
|
326
332
|
|
327
333
|
def teardown
|
328
|
-
super
|
329
334
|
@parent = nil
|
335
|
+
super
|
330
336
|
end
|
331
337
|
|
332
338
|
end
|
@@ -349,8 +355,8 @@ module MotionWiretap
|
|
349
355
|
end
|
350
356
|
|
351
357
|
def teardown
|
352
|
-
super
|
353
358
|
@parent = nil
|
359
|
+
super
|
354
360
|
end
|
355
361
|
|
356
362
|
end
|
@@ -374,8 +380,8 @@ module MotionWiretap
|
|
374
380
|
end
|
375
381
|
|
376
382
|
def teardown
|
377
|
-
super
|
378
383
|
@parent = nil
|
384
|
+
super
|
379
385
|
end
|
380
386
|
|
381
387
|
end
|
@@ -398,23 +404,21 @@ module MotionWiretap
|
|
398
404
|
end
|
399
405
|
|
400
406
|
def teardown
|
401
|
-
super
|
402
407
|
@parent = nil
|
408
|
+
super
|
403
409
|
end
|
404
410
|
|
405
411
|
end
|
406
412
|
|
407
413
|
class WiretapProc < WiretapTarget
|
408
414
|
|
409
|
-
def initialize(target, queue
|
415
|
+
def initialize(target, queue, block)
|
410
416
|
@started = false
|
411
|
-
super(target
|
417
|
+
super(target)
|
418
|
+
and_then(&block) if block
|
412
419
|
queue(queue) if queue
|
413
|
-
end
|
414
420
|
|
415
|
-
|
416
|
-
super
|
417
|
-
start
|
421
|
+
start if block
|
418
422
|
end
|
419
423
|
|
420
424
|
def start
|
@@ -422,7 +426,11 @@ module MotionWiretap
|
|
422
426
|
@started = true
|
423
427
|
enqueue do
|
424
428
|
begin
|
425
|
-
|
429
|
+
if @target.arity == 0
|
430
|
+
@target.call
|
431
|
+
else
|
432
|
+
@target.call(-> (value) { self.trigger_changed(value) })
|
433
|
+
end
|
426
434
|
rescue Exception => error
|
427
435
|
trigger_error(error)
|
428
436
|
else
|
@@ -432,16 +440,33 @@ module MotionWiretap
|
|
432
440
|
end
|
433
441
|
end
|
434
442
|
|
443
|
+
end
|
435
444
|
|
436
|
-
|
437
|
-
|
445
|
+
class WiretapNotification < Wiretap
|
446
|
+
|
447
|
+
def initialize(notification, object, block)
|
448
|
+
@notification = notification
|
449
|
+
@object = object
|
450
|
+
|
451
|
+
NSNotificationCenter.defaultCenter.addObserver(self, selector: 'notify:', name:@notification, object:@object)
|
452
|
+
listen(&block) if block
|
438
453
|
end
|
439
454
|
|
440
|
-
def
|
455
|
+
def notify(notification)
|
456
|
+
trigger_changed(notification.object, notification.userInfo)
|
457
|
+
end
|
458
|
+
|
459
|
+
def teardown
|
460
|
+
NSNotificationCenter.defaultCenter.removeObserver(self, name:@notification, object:@object)
|
441
461
|
super
|
442
|
-
start
|
443
462
|
end
|
444
463
|
|
445
464
|
end
|
446
465
|
|
466
|
+
module_function
|
467
|
+
|
468
|
+
def wiretaps
|
469
|
+
@wiretaps ||= []
|
470
|
+
end
|
471
|
+
|
447
472
|
end
|
@@ -10,11 +10,7 @@ end
|
|
10
10
|
|
11
11
|
class NSArray
|
12
12
|
|
13
|
-
def wiretap(
|
14
|
-
raise "`wiretap` is not supported on Arrays (arrays are not observable). You probably meant to use `wiretaps`."
|
15
|
-
end
|
16
|
-
|
17
|
-
def wiretaps(&block)
|
13
|
+
def wiretap(&block)
|
18
14
|
MotionWiretap::WiretapArray.new(self, &block)
|
19
15
|
end
|
20
16
|
|
@@ -24,7 +20,16 @@ end
|
|
24
20
|
class Proc
|
25
21
|
|
26
22
|
def wiretap(queue=nil, &block)
|
27
|
-
MotionWiretap::WiretapProc(queue,
|
23
|
+
MotionWiretap::WiretapProc.new(self, queue, block)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
class NSString
|
30
|
+
|
31
|
+
def wiretap(object=nil, &block)
|
32
|
+
MotionWiretap::WiretapNotification(self, object, block)
|
28
33
|
end
|
29
34
|
|
30
35
|
end
|
@@ -1,25 +1,33 @@
|
|
1
1
|
module MotionWiretap
|
2
|
+
class GestureNotFound < Exception
|
3
|
+
end
|
2
4
|
|
3
|
-
class WiretapView <
|
5
|
+
class WiretapView < Wiretap
|
4
6
|
|
5
7
|
def on(recognizer, options=nil, &block)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
8
|
+
if recognizer
|
9
|
+
case recognizer
|
10
|
+
when :tap
|
11
|
+
recognizer = Gestures.tap(self, options)
|
12
|
+
when :pinch
|
13
|
+
recognizer = Gestures.pinch(self, options)
|
14
|
+
when :rotate
|
15
|
+
recognizer = Gestures.rotate(self, options)
|
16
|
+
when :swipe
|
17
|
+
recognizer = Gestures.swipe(self, options)
|
18
|
+
when :pan
|
19
|
+
recognizer = Gestures.pan(self, options)
|
20
|
+
when :press
|
21
|
+
recognizer = Gestures.press(self, options)
|
22
|
+
else
|
23
|
+
raise GestureNotFound.new(recognizer.to_s)
|
24
|
+
end
|
25
|
+
|
26
|
+
self.target.addGestureRecognizer(recognizer)
|
19
27
|
end
|
20
28
|
|
21
|
-
|
22
|
-
|
29
|
+
super(&block)
|
30
|
+
|
23
31
|
return self
|
24
32
|
end
|
25
33
|
|
@@ -31,16 +39,18 @@ module MotionWiretap
|
|
31
39
|
|
32
40
|
class WiretapControl < WiretapView
|
33
41
|
|
34
|
-
# control_event can be any
|
35
|
-
# wiretap_control_events.rb, or an array of
|
36
|
-
# symbols.
|
42
|
+
# control_event can be any UIControlEventConstant, or any symbol found in
|
43
|
+
# wiretap_control_events.rb, or an array of UIControlEventConstants or
|
44
|
+
# symbols. Since UIView implements `on` to accept a gesture, this method
|
45
|
+
# calls `super` when the symbol isn't a control
|
37
46
|
def on(control_event, options={}, &block)
|
38
47
|
begin
|
39
48
|
control_event = ControlEvents.convert(control_event)
|
40
49
|
self.target.addTarget(self, action:'handle_event:', forControlEvents:control_event)
|
41
|
-
listen(&block) if block
|
42
50
|
rescue ControlEventNotFound
|
43
51
|
super(control_event, options, &block)
|
52
|
+
else
|
53
|
+
super(nil, options, &block)
|
44
54
|
end
|
45
55
|
|
46
56
|
return self
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: motion-wiretap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-09-24 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: ! 'ReactiveCocoa is an amazing system, and RubyMotion could benefit from
|
15
15
|
the
|