Class: CDQ::CDQContextManager

Inherits:
Object
  • Object
show all
Includes:
Deprecation
Defined in:
motion/cdq/context.rb

Constant Summary collapse

BACKGROUND_SAVE_NOTIFICATION =
'com.infinitered.cdq.context.background_save_completed'
DID_FINISH_IMPORT_NOTIFICATION =
'com.infinitered.cdq.context.did_finish_import'

Instance Method Summary collapse

Methods included from Deprecation

#deprecate

Constructor Details

#initialize(opts = {}) ⇒ CDQContextManager

Returns a new instance of CDQContextManager.


10
11
12
# File 'motion/cdq/context.rb', line 10

def initialize(opts = {})
  @store_manager = opts[:store_manager]
end

Instance Method Details

#allObject

An array of all contexts, from bottom to top of the stack.


67
68
69
# File 'motion/cdq/context.rb', line 67

def all
  stack.dup
end

#background(options = {}, &block) ⇒ Object

Run the supplied block in a new context with a private queue. Once the block exits, the context will be forgotten, so any changes made must be saved within the block.

Note that the CDQ context stack, which is used when deciding what to save with `cdq.save` is stored per-thread, so the stack inside the block is different from the stack outside the block. If you push any more contexts inside, they will also disappear when the thread terminates.

The thread is also unique. If you call `background` multiple times, it will be a different thread each time with no persisted state.

Options:

wait: If true, run the block synchronously

241
242
243
244
245
246
247
248
249
# File 'motion/cdq/context.rb', line 241

def background(options = {}, &block)
  # Create a new private queue context with the main context as its parent
  context = create(NSPrivateQueueConcurrencyType)

  on(context, options) do
    push(context, {}, &block)
  end

end

#create(concurrency_type, options = {}, &block) ⇒ Object

Create a new context by type, setting upstream to the topmost context if available, or to the persistent store coordinator if not. Return the context but do NOT push it onto the stack.

Options:

:named - Assign the context a name, making it available as cdq.contexts.<name>.  The
  name is permanent, and should only be used for contexts that are intended to be global,
  since the object will never get released.

99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'motion/cdq/context.rb', line 99

def create(concurrency_type, options = {}, &block)
  @has_been_set_up = true

  case concurrency_type
  when :main
    context = NSManagedObjectContext.alloc.initWithConcurrencyType(NSMainQueueConcurrencyType)
    options[:named] = :main unless options.has_key?(:named)
  when :private_queue, :private
    context = NSManagedObjectContext.alloc.initWithConcurrencyType(NSPrivateQueueConcurrencyType)
  when :root
    context = NSManagedObjectContext.alloc.initWithConcurrencyType(NSPrivateQueueConcurrencyType)
    options[:named] = :root unless options.has_key?(:named)
  else
    context = NSManagedObjectContext.alloc.initWithConcurrencyType(concurrency_type)
  end

  if stack.empty?
    if @store_manager.invalid?
      raise "store coordinator not found. Cannot create the first context without one."
    else
      context.mergePolicy = NSMergePolicy.alloc.initWithMergeType(NSMergeByPropertyObjectTrumpMergePolicyType)
      context.performBlockAndWait ->{
        coordinator = @store_manager.current
        context.persistentStoreCoordinator = coordinator
        #Dispatch::Queue.main.async {
        NSNotificationCenter.defaultCenter.addObserver(self, selector:"did_finish_import:", name:NSPersistentStoreDidImportUbiquitousContentChangesNotification, object:nil)
        @observed_context = context
        #}
      }
    end
  else
    context.parentContext = stack.last
  end

  if options[:named]
    if respond_to?(options[:named])
      raise "Cannot name a context '#{options[:named]}': conflicts with existing method"
    end
    self.class.send(:define_method, options[:named]) do
      context
    end
  end
  context
end

#currentObject

The current context at the top of the stack.


58
59
60
61
62
63
# File 'motion/cdq/context.rb', line 58

def current
  if stack.empty? && !@has_been_set_up
    push(NSMainQueueConcurrencyType)
  end
  stack.last
end

#deallocObject


14
15
16
17
# File 'motion/cdq/context.rb', line 14

def dealloc
  NSNotificationCenter.defaultCenter.removeObserver(self) if @observed_context
  super
end

#did_finish_import(notification) ⇒ Object


271
272
273
274
275
276
# File 'motion/cdq/context.rb', line 271

def did_finish_import(notification)
  @observed_context.performBlockAndWait ->{
    @observed_context.mergeChangesFromContextDidSaveNotification(notification)
    NSNotificationCenter.defaultCenter.postNotificationName(DID_FINISH_IMPORT_NOTIFICATION, object:self, userInfo:{context: @observed_context})
  }
end

#new(concurrency_type, &block) ⇒ Object

Create and push a new context with the specified concurrency type. Its parent will be set to the previous head context. If a block is supplied, the new context will exist for the duration of the block and then the previous state will be restore_managerd.

REMOVE1.1


83
84
85
86
87
# File 'motion/cdq/context.rb', line 83

def new(concurrency_type, &block)
  deprecate "cdq.contexts.new() is deprecated.  Use push() or create()"
  context = create(concurrency_type)
  push(context, {}, &block)
end

#on(context, options = {}, &block) ⇒ Object

Run a block on the supplied context using performBlock. If context is a symbol, it will look up the corresponding named context and use that instead.

Options:

wait: If true, run the block synchronously

258
259
260
261
262
263
264
265
266
267
268
269
# File 'motion/cdq/context.rb', line 258

def on(context, options = {}, &block)

  if context.is_a? Symbol
    context = send(context)
  end

  if options[:wait]
    context.performBlockAndWait(block)
  else
    context.performBlock(block)
  end
end

#pop(&block) ⇒ Object

Pop the top context off the stack. If a block is supplied, pop for the duration of the block and then return to the previous state.


44
45
46
47
48
49
50
51
52
53
54
# File 'motion/cdq/context.rb', line 44

def pop(&block)
  if block_given?
    save_stack do
      rval = pop_from_stack
      block.call
      rval
    end
  else
    pop_from_stack
  end
end

#push(context, options = {}, &block) ⇒ Object

Push a new context onto the stack for the current thread, making that context the default. If a block is supplied, push for the duration of the block and then return to the previous state.


23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'motion/cdq/context.rb', line 23

def push(context, options = {}, &block)
  @has_been_set_up = true

  unless context.is_a? NSManagedObjectContext
    context = create(context, options)
  end

  if block_given?
    save_stack do
      context = push_to_stack(context)
      block.call
      context
    end
  else
    push_to_stack(context)
  end
end

#reset!Object

Remove all contexts.


73
74
75
# File 'motion/cdq/context.rb', line 73

def reset!
  self.stack = []
end

#save(*contexts_and_options) ⇒ Object

Save all passed contexts in order. If none are supplied, save all contexts in the stack, starting with the current and working down. If you pass a symbol instead of a context, it will look up context with that name.

Options:

always_wait: If true, force use of performBlockAndWait for synchronous
  saves.  By default, private queue saves are performed asynchronously.
  Main queue saves are always synchronous if performed from the main
  queue.

156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'motion/cdq/context.rb', line 156

def save(*contexts_and_options)

  if contexts_and_options.last.is_a? Hash
    options = contexts_and_options.pop
  else
    options = {}
  end

  if contexts_and_options.empty?
    contexts = stack.reverse
  else
    # resolve named contexts
    contexts = contexts_and_options.map do |c|
      if c.is_a? Symbol
        send(c)
      else
        c
      end
    end
  end

  set_timestamps
  always_wait = options[:always_wait]
  contexts.each do |context|
    if context.concurrencyType == NSMainQueueConcurrencyType && NSThread.isMainThread
      with_error_object do |error|
        context.save(error)
      end
    elsif always_wait
      context.performBlockAndWait( -> {

        with_error_object do |error|
          context.save(error)
        end

      } )
    elsif context.concurrencyType == NSPrivateQueueConcurrencyType
      task_id = UIApplication.sharedApplication.beginBackgroundTaskWithExpirationHandler( -> { NSLog "CDQ Save Timed Out" } )

      if task_id == UIBackgroundTaskInvalid
        context.performBlockAndWait( -> {

          with_error_object do |error|
            context.save(error)
          end

        } )
      else
        context.performBlock( -> {

          # Let the application know we're doing something important
          with_error_object do |error|
            context.save(error)
          end

          UIApplication.sharedApplication.endBackgroundTask(task_id)

          NSNotificationCenter.defaultCenter.postNotificationName(BACKGROUND_SAVE_NOTIFICATION, object: context)

        } )
      end
    else
      with_error_object do |error|
        context.save(error)
      end
    end
  end
  true
end