// // NSArrayAdditions.m // HigherOrderMessaging // // Created by Ofri Wolfus on 24/09/05. // Copyright 2005 Ofri Wolfus. All rights reserved. // #import "NSArrayHOMAdditions.h" #import #import "HOMUtilities.h" #include #import "HOMIteratedArgument.h" #include //Pointer to objectAtIndex: method of NSArray. //This was already declared in HOMIteratedVarWrapper.h //typedef id (* IMPWithUnsignedArg)(id, SEL, unsigned); /*@interface NSArray (HOM_Private) - (id)getObjectsWhere:(NSArray *)messages returned:(BOOL)resultValue; - (id)getResultsForInvocation:(NSInvocation *)invocation NSNullForNil:(BOOL)useNSNull; - (id)getResultsForSelector:(SEL)sel args:(marg_list)args NSNullForNil:(BOOL)useNSNull; - (id)singleSelect:(NSInvocation *)invocation; - (id)singleSelectWhere:(NSArray *)invocations; //- (id)pickSingle:(NSInvocation *)invocation value:(BOOL)returnValue; - (id)pickSingleWithResult:(BOOL)returnValue selector:(SEL)sel args:(marg_list)args; - (id)singleReject:(NSInvocation *)invocation; - (id)getObjectWhere:(NSArray *)messages returned:(BOOL)resultValue; @end*/ //Some function prototypes //The objc compiler automatically casts objc_msgSend() to the currect type, and since we do it by hand, //we have to also cast it ourselves. /*typedef BOOL (* hom_BOOL_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame); typedef char (* hom_char_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame); typedef double (* hom_double_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame); typedef float (* hom_float_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame); typedef int (* hom_int_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame); typedef long (* hom_long_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame); typedef long long (* hom_long_long_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame); typedef short (* hom_short_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame); typedef unsigned char (* hom_unsigned_char_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame); typedef unsigned int (* hom_unsigned_int_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame); typedef unsigned long (* hom_unsigned_long_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame); typedef unsigned long long (* hom_unsigned_long_long_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame); typedef unsigned short (* hom_unsigned_short_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame); typedef long double (* hom_long_double_msgSendv)(id self, SEL op, unsigned arg_size, marg_list arg_frame); //Not supported by NSNumber //#warning NSNumber doesn't support long double yet. We store long doubles in NSValue for now, but a better solution is needed.*/ //Pointer to objectAtIndex: method of NSArray typedef id (* IMPWithUnsignedArg)(id, SEL, unsigned); @implementation NSArray (HOMIteration) extern Method _hom_findMethodForSelector(SEL sel); - (id)each { return [HOMIteratedArgument iteratedArgumentWithArray:self]; } // Should this be changed to be a varadic method? - (id)collect:(Message *)msg { SEL sel = [msg selector]; marg_list origArgs = [msg arguments]; unsigned argsSize = [msg argumentsSize]; marg_list args = malloc(malloc_size(origArgs)); unsigned argCount = hom_getNumberOfArguments(sel); unsigned i; Class iteratedArgCls = [HOMIteratedArgument class]; unsigned count = [self count]; char *types = calloc(argCount, sizeof(char)); Method meth = _hom_findMethodForSelector(sel); NSMutableArray *result = nil; IMP addObject = NULL; IMP objectAtIndex = [self methodForSelector:@selector(objectAtIndex:)]; if (count > 0U) { if (*(method_getTypeEncoding(meth)) == _C_ID || *(method_getTypeEncoding(meth)) == _C_CLASS) { result = [[[NSMutableArray alloc] initWithCapacity:count] autorelease]; addObject = [result methodForSelector:@selector(addObject:)]; } // First, copy the arguments list since we're going to modify it memcpy(args, origArgs, malloc_size(args)); // Scan the arguments of our message and find instances of HOMIteratedArgument. // Our types array will mark the places of these with the letter 'z' so we don't have to check again. // This loop also finds the largest number of arguments that needs to be iterated, and stores it in count. for (i = 2U; i < argCount; i++) { types[i] = *hom_getArgumentTypeAtIndex(meth, i); if (types[i] == _C_ID) { if ([marg_getValue(args, hom_getArgumentOffsetAtIndex(meth, i), id) isKindOfClass:iteratedArgCls]) types[i] = 'z'; // This is our special mark for an iterated argument } } // Loop through all objects in all our iterated arguments, update our args list, and resend it for (i = 0U; i < count; i++) { unsigned j; // Update the remaining arguments in our list for (j = 2U; j < argCount; j++) { if (types[j] == 'z') { int offset = hom_getArgumentOffsetAtIndex(meth, j); HOMIteratedArgument *arg = marg_getValue(origArgs, offset, HOMIteratedArgument *); marg_setValue(args, offset, id, [arg nextObject]); // When there are no arguments left for this argument iterator, we mark its type as id // so we'll skip it in the next run. if ([arg isFinished]) types[j] = _C_ID; } } // Send the updated arguments list addObject(result, @selector(addObject:), objc_msgSendv(objectAtIndex(self, @selector(objectAtIndex:), i), sel, argsSize, args)); } // Clean up free(types); free(args); } return result ?: self; } //======================================================== //===================== Select Where ===================== //======================================================== #pragma mark Select Where - (id)getObjectsWhereFirstMessage:(Message *)firstMsg additionalMessages:(va_list)messages returned:(BOOL)resultValue { Message *msg; unsigned i, count = [self count]; IMPWithUnsignedArg objectAtIndexIMP = (IMPWithUnsignedArg)[self methodForSelector:@selector(objectAtIndex:)]; va_list msges; // Allocating with a non-zero capacity is probably a good idea here. // TODO: Do it! CFMutableArrayRef result = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); // Loop through all our objects, and send it all the messages passed to us. // All messages except the last are assumed to return id, and the last one should return a BOOL // (although any integer value will work). for (i = 0U; i < count; i++) { id object = objectAtIndexIMP(self, @selector(objectAtIndex:), i); // Send the first message to our object and store the result. // Since id is a pointer, and a pointer can hold BOOL values, // we're fine even if this is the only message. id obj = objc_msgSendv(object, [firstMsg selector], [firstMsg argumentsSize], [firstMsg arguments]); // Send every other messages to the previous result. // We assume the result is an object. If it's not, bad things will happen, but that's not our fault. // NOTE: We don't verify that the receiver responds to the actual message like -select does // which give us a nice speed boost, but may also cause an exception to be thrown. // TODO: Cache the IMPs of -[Message selector], -[Message argumentsSize] and -[Message arguments]. va_copy(msges, messages); while ((msg = va_arg(msges, Message *))) obj = objc_msgSendv(obj, [msg selector], [msg argumentsSize], [msg arguments]); va_end(msges); // Check if the final result equals the result value we're looking for. // If true, we'll add the original object to our results array. if ((BOOL)(int)obj == resultValue) CFArrayAppendValue(result, object); } // Clean up va_end(messages); // Make our return value pretty if (CFArrayGetCount(result) == 0) { CFRelease(result); result = nil; } else { result = (CFMutableArrayRef)[(NSArray *)result autorelease]; } return (id)result; } - (id)selectWhere:(Message *)firstMessage, ... { va_list va; DPAssert(firstMessage != nil, @"The passed message must not be nil"); va_start(va, firstMessage); return [self getObjectsWhereFirstMessage:firstMessage additionalMessages:va returned:YES]; } - (id)rejectWhere:(Message *)firstMessage, ... { va_list va; DPAssert(firstMessage != nil, @"The passed message must not be nil"); va_start(va, firstMessage); return [self getObjectsWhereFirstMessage:firstMessage additionalMessages:va returned:NO]; } //============================================================== //================= Select/Reject Single Where ================= //============================================================== #pragma mark Select/Reject Single Where - (id)getObjectWhereFirstMsg:(Message *)firstMsg additionalMessages:(va_list)messages returned:(BOOL)resultValue index:(unsigned *)index { Message *msg; unsigned i, count = [self count]; IMPWithUnsignedArg objectAtIndexIMP = (IMPWithUnsignedArg)[self methodForSelector:@selector(objectAtIndex:)]; va_list msges; // Loop through all our objects, and send it all the messages passed to us. // All messages except the last are assumed to return id, and the last one should return a BOOL // (although any integer value will work). for (i = 0U; i < count; i++) { id object = objectAtIndexIMP(self, @selector(objectAtIndex:), i); // Send the first message to our object and store the result. // Since id is a pointer, and a pointer can hold BOOL values, // we're fine even if this is the only message. id obj = objc_msgSendv(object, [firstMsg selector], [firstMsg argumentsSize], [firstMsg arguments]); // Send every other messages to the previous result. // We assume the result is an object. If it's not, bad things will happen, but that's not our fault. // NOTE: We don't verify that the receiver responds to the actual message like -select does // which give us a nice speed boost, but may also cause an exception to be thrown. // TODO: Cache the IMPs of -[Message selector], -[Message argumentsSize] and -[Message arguments]. va_copy(msges, messages); while ((msg = va_arg(msges, Message *))) obj = objc_msgSendv(obj, [msg selector], [msg argumentsSize], [msg arguments]); va_end(msges); // Check if the final result equals the result value we're looking for. // If true, we'll return the original object + its index. if ((BOOL)(int)obj == resultValue) { if (index) *index = i; return object; } } // Clean up va_end(messages); return nil; } - (id)selectSingleWhere:(Message *)firstMessage, ... { va_list va; DPAssert(firstMessage != nil, @"The passed message must not be nil"); va_start(va, firstMessage); return [self getObjectWhereFirstMsg:firstMessage additionalMessages:va returned:YES index:NULL]; } - (id)rejectSingleWhere:(Message *)firstMessage, ... { va_list va; id object; unsigned i; DPAssert(firstMessage != nil, @"The passed message must not be nil"); va_start(va, firstMessage); object = [self getObjectWhereFirstMsg:firstMessage additionalMessages:va returned:YES index:&i]; if (object) { NSMutableArray *arr = [self mutableCopy]; [arr removeObjectAtIndex:i]; return [arr autorelease]; } return nil; } @end @implementation NSArray (HOMAdditions) //Invoke an invocation with any of our objects - (void)makeObjectsPerformInvocation:(NSInvocation *)invocation { SEL selector = [invocation selector]; if (invocation) { for (unsigned i = 0; i < [self count]; ++i) { id object = [self objectAtIndex:i]; if ([object respondsToSelector:selector]) [invocation invokeWithTarget:object]; } } else { NSException *exception = [NSException exceptionWithName:NSInvalidArgumentException reason:@"invocation must not be nil!" userInfo:nil]; [exception raise]; } } @end @implementation NSArray (BlocksSupport) /*- (void)foreach:(Block)b { if (b) { if (!block_is_initialized(b)) { unsigned *count = malloc(sizeof(unsigned)); unsigned *index = malloc(sizeof(unsigned)); *count = [self count]; *index = 0U; block_set_value_for_key(b, count, @"count"); block_set_value_for_key(b, index, @"index"); block_set_value_for_key(b, [self objectAtIndex:0U], @"each"); block_process(b); } else { unsigned *count = block_get_value_for_key(b, @"count"); unsigned *index = block_get_value_for_key(b, @"index"); ++(*index); if (*index < *count) { block_set_value_for_key(b, [self objectAtIndex:*index], @"each"); block_process(b); } else { free(count); free(index); //block_free(b); } } } }*/ #if 0 - (void)foreach:(DPBlock *)b { unsigned i, count = [self count]; for (i = 0U; i < count; i++) { [b setValue:[self objectAtIndex:i] forKey:@"each"]; [b process]; } } #endif @end