/libs/ObjectAL/Session/OALSuspendHandler.m

http://github.com/kstenerud/ObjectAL-for-iPhone · Objective C · 229 lines · 148 code · 23 blank · 58 comment · 29 complexity · 905a7d12676f5a89e985343bbd3f3302 MD5 · raw file

  1. //
  2. // OALSuspendHandler.m
  3. // ObjectAL
  4. //
  5. // Created by Karl Stenerud on 10-12-19.
  6. //
  7. // Copyright 2010 Karl Stenerud
  8. //
  9. // Licensed under the Apache License, Version 2.0 (the "License");
  10. // you may not use this file except in compliance with the License.
  11. // You may obtain a copy of the License at
  12. //
  13. // http://www.apache.org/licenses/LICENSE-2.0
  14. //
  15. // Unless required by applicable law or agreed to in writing, software
  16. // distributed under the License is distributed on an "AS IS" BASIS,
  17. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. // See the License for the specific language governing permissions and
  19. // limitations under the License.
  20. //
  21. // Note: You are NOT required to make the license available from within your
  22. // iOS application. Including it in your project is sufficient.
  23. //
  24. // Attribution is not required, but appreciated :)
  25. //
  26. #import "OALSuspendHandler.h"
  27. #import "NSMutableArray+WeakReferences.h"
  28. #import <objc/message.h>
  29. @implementation OALSuspendHandler
  30. + (OALSuspendHandler*) handlerWithTarget:(id) target selector:(SEL) selector
  31. {
  32. return [[[self alloc] initWithTarget:target selector:selector] autorelease];
  33. }
  34. - (id) initWithTarget:(id) target selector:(SEL) selector
  35. {
  36. if(nil != (self = [super init]))
  37. {
  38. listeners = [NSMutableArray newMutableArrayUsingWeakReferencesWithCapacity:10];
  39. manualSuspendStates = [[NSMutableArray alloc] initWithCapacity:10];
  40. suspendStatusChangeTarget = target;
  41. suspendStatusChangeSelector = selector;
  42. }
  43. return self;
  44. }
  45. - (void) dealloc
  46. {
  47. [listeners release];
  48. [manualSuspendStates release];
  49. [super dealloc];
  50. }
  51. - (void) addSuspendListener:(id<OALSuspendListener>) listener
  52. {
  53. @synchronized(self)
  54. {
  55. [listeners addObject:listener];
  56. // If this handler is already suspended, make sure we don't unsuspend
  57. // a newly added listener on the next manual unsuspend.
  58. bool startingSuspendedValue = manualSuspendLock ? listener.manuallySuspended : NO;
  59. [manualSuspendStates addObject:[NSNumber numberWithBool:startingSuspendedValue]];
  60. }
  61. }
  62. - (void) removeSuspendListener:(id<OALSuspendListener>) listener
  63. {
  64. @synchronized(self)
  65. {
  66. NSUInteger index = [listeners indexOfObject:listener];
  67. if(NSNotFound != index)
  68. {
  69. [listeners removeObjectAtIndex:index];
  70. [manualSuspendStates removeObjectAtIndex:index];
  71. }
  72. }
  73. }
  74. - (bool) manuallySuspended
  75. {
  76. @synchronized(self)
  77. {
  78. return manualSuspendLock;
  79. }
  80. }
  81. - (void) setManuallySuspended:(bool) value
  82. {
  83. /* This handler propagates all suspend/unsuspend events to all listeners.
  84. * An unsuspend will occur in the reverse order to a suspend (meaning, it will
  85. * unsuspend listeners in the reverse order that it suspended them).
  86. * On suspend, all listeners will be suspended prior to suspending this handler's
  87. * slave object. On unsuspend, all listeners will resume after the slave object.
  88. *
  89. * Since "suspended" is manually triggered, this handler records all listeners'
  90. * suspend states so that it can intelligently decide whether to unsuspend or
  91. * not.
  92. */
  93. @synchronized(self)
  94. {
  95. // Setting must occur in the opposite order to clearing.
  96. if(value)
  97. {
  98. NSUInteger numListeners = [listeners count];
  99. for(NSUInteger index = 0; index < numListeners; index++)
  100. {
  101. id<OALSuspendListener> listener = [listeners objectAtIndex:index];
  102. // Record whether they were already suspended or not
  103. bool alreadySuspended = listener.manuallySuspended;
  104. if(alreadySuspended != [[manualSuspendStates objectAtIndex:index] boolValue])
  105. {
  106. [manualSuspendStates replaceObjectAtIndex:index withObject:[NSNumber numberWithBool:alreadySuspended]];
  107. }
  108. // Update listener suspend state if necessary
  109. if(!alreadySuspended)
  110. {
  111. listener.manuallySuspended = YES;
  112. }
  113. }
  114. }
  115. /* If the new value is the same as the old, do nothing.
  116. * If the other lock is set, do nothing.
  117. * Otherwise, send a suspend/unsuspend event to the slave.
  118. */
  119. if(value != manualSuspendLock)
  120. {
  121. manualSuspendLock = value;
  122. if(!interruptLock)
  123. {
  124. if(nil != suspendStatusChangeTarget)
  125. {
  126. objc_msgSend(suspendStatusChangeTarget, suspendStatusChangeSelector, manualSuspendLock);
  127. }
  128. }
  129. }
  130. // Ensure clearing occurs in opposing order
  131. if(!value)
  132. {
  133. for(int index = (int)[listeners count] - 1; index >= 0; index--)
  134. {
  135. id<OALSuspendListener> listener = [listeners objectAtIndex:index];
  136. bool alreadySuspended = [[manualSuspendStates objectAtIndex:index] boolValue];
  137. // Update listener suspend state if necessary
  138. if(!alreadySuspended && listener.manuallySuspended)
  139. {
  140. listener.manuallySuspended = NO;
  141. }
  142. }
  143. }
  144. }
  145. }
  146. - (bool) interrupted
  147. {
  148. @synchronized(self)
  149. {
  150. return interruptLock;
  151. }
  152. }
  153. - (void) setInterrupted:(bool) value
  154. {
  155. /* This handler propagates all interrupt/end interrupt events to all listeners.
  156. * An end interrupt will occur in the reverse order to an interrupt (meaning, it will
  157. * end interrupt on listeners in the reverse order that it interrupted them).
  158. * On interrupt, all listeners will be interrupted prior to suspending this handler's
  159. * slave object. On end interrupt, all listeners will end interrupt after the slave object.
  160. */
  161. @synchronized(self)
  162. {
  163. // Setting must occur in the opposite order to clearing.
  164. if(value)
  165. {
  166. for(id<OALSuspendListener> listener in listeners)
  167. {
  168. if(!listener.interrupted)
  169. {
  170. listener.interrupted = YES;
  171. }
  172. }
  173. }
  174. /* If the new value is the same as the old, do nothing.
  175. * If the other lock is set, do nothing.
  176. * Otherwise, send a suspend/unsuspend event to the slave.
  177. */
  178. if(value != interruptLock)
  179. {
  180. interruptLock = value;
  181. if(!manualSuspendLock)
  182. {
  183. if(nil != suspendStatusChangeTarget)
  184. {
  185. objc_msgSend(suspendStatusChangeTarget, suspendStatusChangeSelector, interruptLock);
  186. }
  187. }
  188. }
  189. // Ensure clearing occurs in opposing order
  190. if(!value)
  191. {
  192. for(id<OALSuspendListener> listener in [listeners reverseObjectEnumerator])
  193. {
  194. if(listener.interrupted)
  195. {
  196. listener.interrupted = NO;
  197. }
  198. }
  199. }
  200. }
  201. }
  202. - (bool) suspended
  203. {
  204. return interruptLock | manualSuspendLock;
  205. }
  206. @end