/libs/ObjectAL/Session/OALSuspendHandler.m
Objective C | 229 lines | 148 code | 23 blank | 58 comment | 29 complexity | 905a7d12676f5a89e985343bbd3f3302 MD5 | raw file
Possible License(s): Apache-2.0
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 27#import "OALSuspendHandler.h" 28#import "NSMutableArray+WeakReferences.h" 29#import <objc/message.h> 30 31@implementation OALSuspendHandler 32 33+ (OALSuspendHandler*) handlerWithTarget:(id) target selector:(SEL) selector 34{ 35 return [[[self alloc] initWithTarget:target selector:selector] autorelease]; 36} 37 38- (id) initWithTarget:(id) target selector:(SEL) selector 39{ 40 if(nil != (self = [super init])) 41 { 42 listeners = [NSMutableArray newMutableArrayUsingWeakReferencesWithCapacity:10]; 43 manualSuspendStates = [[NSMutableArray alloc] initWithCapacity:10]; 44 suspendStatusChangeTarget = target; 45 suspendStatusChangeSelector = selector; 46 } 47 return self; 48} 49 50- (void) dealloc 51{ 52 [listeners release]; 53 [manualSuspendStates release]; 54 55 [super dealloc]; 56} 57 58- (void) addSuspendListener:(id<OALSuspendListener>) listener 59{ 60 @synchronized(self) 61 { 62 [listeners addObject:listener]; 63 // If this handler is already suspended, make sure we don't unsuspend 64 // a newly added listener on the next manual unsuspend. 65 bool startingSuspendedValue = manualSuspendLock ? listener.manuallySuspended : NO; 66 [manualSuspendStates addObject:[NSNumber numberWithBool:startingSuspendedValue]]; 67 } 68} 69 70- (void) removeSuspendListener:(id<OALSuspendListener>) listener 71{ 72 @synchronized(self) 73 { 74 NSUInteger index = [listeners indexOfObject:listener]; 75 if(NSNotFound != index) 76 { 77 [listeners removeObjectAtIndex:index]; 78 [manualSuspendStates removeObjectAtIndex:index]; 79 } 80 } 81} 82 83- (bool) manuallySuspended 84{ 85 @synchronized(self) 86 { 87 return manualSuspendLock; 88 } 89} 90 91- (void) setManuallySuspended:(bool) value 92{ 93 /* This handler propagates all suspend/unsuspend events to all listeners. 94 * An unsuspend will occur in the reverse order to a suspend (meaning, it will 95 * unsuspend listeners in the reverse order that it suspended them). 96 * On suspend, all listeners will be suspended prior to suspending this handler's 97 * slave object. On unsuspend, all listeners will resume after the slave object. 98 * 99 * Since "suspended" is manually triggered, this handler records all listeners' 100 * suspend states so that it can intelligently decide whether to unsuspend or 101 * not. 102 */ 103 104 @synchronized(self) 105 { 106 // Setting must occur in the opposite order to clearing. 107 if(value) 108 { 109 NSUInteger numListeners = [listeners count]; 110 for(NSUInteger index = 0; index < numListeners; index++) 111 { 112 id<OALSuspendListener> listener = [listeners objectAtIndex:index]; 113 114 // Record whether they were already suspended or not 115 bool alreadySuspended = listener.manuallySuspended; 116 if(alreadySuspended != [[manualSuspendStates objectAtIndex:index] boolValue]) 117 { 118 [manualSuspendStates replaceObjectAtIndex:index withObject:[NSNumber numberWithBool:alreadySuspended]]; 119 } 120 121 // Update listener suspend state if necessary 122 if(!alreadySuspended) 123 { 124 listener.manuallySuspended = YES; 125 } 126 } 127 } 128 129 /* If the new value is the same as the old, do nothing. 130 * If the other lock is set, do nothing. 131 * Otherwise, send a suspend/unsuspend event to the slave. 132 */ 133 if(value != manualSuspendLock) 134 { 135 manualSuspendLock = value; 136 if(!interruptLock) 137 { 138 if(nil != suspendStatusChangeTarget) 139 { 140 objc_msgSend(suspendStatusChangeTarget, suspendStatusChangeSelector, manualSuspendLock); 141 } 142 } 143 } 144 145 // Ensure clearing occurs in opposing order 146 if(!value) 147 { 148 for(int index = (int)[listeners count] - 1; index >= 0; index--) 149 { 150 id<OALSuspendListener> listener = [listeners objectAtIndex:index]; 151 152 bool alreadySuspended = [[manualSuspendStates objectAtIndex:index] boolValue]; 153 154 // Update listener suspend state if necessary 155 if(!alreadySuspended && listener.manuallySuspended) 156 { 157 listener.manuallySuspended = NO; 158 } 159 } 160 } 161 } 162} 163 164- (bool) interrupted 165{ 166 @synchronized(self) 167 { 168 return interruptLock; 169 } 170} 171 172- (void) setInterrupted:(bool) value 173{ 174 /* This handler propagates all interrupt/end interrupt events to all listeners. 175 * An end interrupt will occur in the reverse order to an interrupt (meaning, it will 176 * end interrupt on listeners in the reverse order that it interrupted them). 177 * On interrupt, all listeners will be interrupted prior to suspending this handler's 178 * slave object. On end interrupt, all listeners will end interrupt after the slave object. 179 */ 180 @synchronized(self) 181 { 182 // Setting must occur in the opposite order to clearing. 183 if(value) 184 { 185 for(id<OALSuspendListener> listener in listeners) 186 { 187 if(!listener.interrupted) 188 { 189 listener.interrupted = YES; 190 } 191 } 192 } 193 194 /* If the new value is the same as the old, do nothing. 195 * If the other lock is set, do nothing. 196 * Otherwise, send a suspend/unsuspend event to the slave. 197 */ 198 if(value != interruptLock) 199 { 200 interruptLock = value; 201 if(!manualSuspendLock) 202 { 203 if(nil != suspendStatusChangeTarget) 204 { 205 objc_msgSend(suspendStatusChangeTarget, suspendStatusChangeSelector, interruptLock); 206 } 207 } 208 } 209 210 // Ensure clearing occurs in opposing order 211 if(!value) 212 { 213 for(id<OALSuspendListener> listener in [listeners reverseObjectEnumerator]) 214 { 215 if(listener.interrupted) 216 { 217 listener.interrupted = NO; 218 } 219 } 220 } 221 } 222} 223 224- (bool) suspended 225{ 226 return interruptLock | manualSuspendLock; 227} 228 229@end