Index: src/OFArray.h ================================================================== --- src/OFArray.h +++ src/OFArray.h @@ -230,14 +230,14 @@ - (ObjectType)objectAtIndexedSubscript: (size_t)index; /*! * @brief Returns the value for the specified key * - * If the key starts with an `@`, the `@` is stripped and - * `[super valueForKey:]` is called. - * If the key does not start with an `@`, a new array with the value for the - * specified key for each object is returned. + * A new array with the value for the specified key for each object is + * returned. + * + * The special key `@count` can be used to retrieve the count as an OFNumber. * * @note Any nil values are replaced with @ref OFNull! * * @param key The key of the value to return * @return The value for the specified key @@ -245,21 +245,18 @@ - (nullable id)valueForKey: (OFString *)key; /*! * @brief Set the value for the specified key * - * If the key starts with an `@`, the `@` is stripped and - * `[super setValue:forKey:]` is called. - * If the key does not start with an `@`, @ref setValue:forKey: is called for - * each object. + * @ref setValue:forKey: is called for each object in the array. * * @note A @ref OFNull value is translated to nil! * * @param value The value for the specified key * @param key The key of the value to set */ -- (void)setValue: (id)value +- (void)setValue: (nullable id)value forKey: (OFString *)key; /*! * @brief Copies the objects at the specified range to the specified buffer. * Index: src/OFArray.m ================================================================== --- src/OFArray.m +++ src/OFArray.m @@ -276,20 +276,12 @@ - (id)valueForKey: (OFString *)key { id ret; - if ([key hasPrefix: @"@"]) { - void *pool = objc_autoreleasePoolPush(); - - key = [key substringWithRange: of_range(1, key.length - 1)]; - ret = [[super valueForKey: key] retain]; - - objc_autoreleasePoolPop(pool); - - return [ret autorelease]; - } + if ([key isEqual: @"@count"]) + return [super valueForKey: @"count"]; ret = [OFMutableArray arrayWithCapacity: self.count]; for (id object in self) { id value = [object valueForKey: key]; @@ -306,21 +298,10 @@ } - (void)setValue: (id)value forKey: (OFString *)key { - if ([key hasPrefix: @"@"]) { - void *pool = objc_autoreleasePoolPush(); - - key = [key substringWithRange: of_range(1, key.length - 1)]; - [super setValue: value - forKey: key]; - - objc_autoreleasePoolPop(pool); - return; - } - for (id object in self) [object setValue: value forKey: key]; } Index: src/OFDictionary.h ================================================================== --- src/OFDictionary.h +++ src/OFDictionary.h @@ -213,33 +213,29 @@ /*! * @brief Returns the value for the given key or `nil` if the key was not * found. * - * If the key starts with an `@`, the `@` is stripped and - * `[super valueForKey:]` is called. - * If the key does not start with an `@`, this is equivalent to - * @ref objectForKey:. + * This is equivalent to @ref objectForKey:. + * + * The special key `@count` can be used to retrieve the count as an OFNumber. * * @param key The key whose value should be returned * @return The value for the given key or `nil` if the key was not found */ - (nullable id)valueForKey: (OFString *)key; /*! * @brief Sets a value for a key. * - * If the key starts with an `@`, the `@` is stripped and - * `[super setValue:forKey:]` is called. - * If the key does not start with an `@`, this is equivalent to - * OFMutableDictionary#setObject:forKey:. In this case, if the dictionary is - * immutable, an @ref OFUndefinedKeyException is thrown. + * This is equivalent to OFMutableDictionary#setObject:forKey:. If the + * dictionary is immutable, an @ref OFUndefinedKeyException is thrown. * * @param key The key to set * @param value The value to set the key to */ -- (void)setValue: (id)value +- (void)setValue: (nullable id)value forKey: (OFString *)key; /*! * @brief Checks whether the dictionary contains an object equal to the * specified object. Index: src/OFDictionary.m ================================================================== --- src/OFDictionary.m +++ src/OFDictionary.m @@ -348,39 +348,19 @@ return [self objectForKey: key]; } - (id)valueForKey: (OFString *)key { - if ([key hasPrefix: @"@"]) { - void *pool = objc_autoreleasePoolPush(); - id ret; - - key = [key substringWithRange: of_range(1, key.length - 1)]; - ret = [[super valueForKey: key] retain]; - - objc_autoreleasePoolPop(pool); - - return [ret autorelease]; - } + if ([key isEqual: @"@count"]) + return [super valueForKey: @"count"]; return [self objectForKey: key]; } - (void)setValue: (id)value forKey: (OFString *)key { - if ([key hasPrefix: @"@"]) { - void *pool = objc_autoreleasePoolPush(); - - key = [key substringWithRange: of_range(1, key.length - 1)]; - [super setValue: value - forKey: key]; - - objc_autoreleasePoolPop(pool); - return; - } - if (![self isKindOfClass: [OFMutableDictionary class]]) @throw [OFUndefinedKeyException exceptionWithObject: self key: key value: value]; Index: src/OFKeyValueCoding.h ================================================================== --- src/OFKeyValueCoding.h +++ src/OFKeyValueCoding.h @@ -29,17 +29,25 @@ * Key Value Coding makes it possible to access properties dynamically using * the interface described by this protocol. */ @protocol OFKeyValueCoding /*! - * @brief Returns the value for the specified key + * @brief Returns the value for the specified key. * * @param key The key of the value to return * @return The value for the specified key */ - (nullable id)valueForKey: (OFString *)key; +/*! + * @brief Returns the value for the specified key path. + * + * @param keyPath The key path of the value to return + * @return The value for the specified key path + */ +- (nullable id)valueForKeyPath: (OFString *)keyPath; + /*! * @brief This is called by @ref valueForKey: if the specified key does not * exist. * * By default, this throws an @ref OFUndefinedKeyException. @@ -48,18 +56,27 @@ * @return The value for the specified undefined key */ - (nullable id)valueForUndefinedKey: (OFString *)key; /*! - * @brief Set the value for the specified key + * @brief Set the value for the specified key. * * @param value The value for the specified key * @param key The key of the value to set */ -- (void)setValue: (id)value +- (void)setValue: (nullable id)value forKey: (OFString *)key; +/*! + * @brief Set the value for the specified key path. + * + * @param value The value for the specified key path + * @param keyPath The key path of the value to set + */ +- (void)setValue: (nullable id)value + forKeyPath: (OFString *)keyPath; + /*! * @brief This is called by @ref setValue:forKey: if the specified key does not * exist. * * By default, this throws an @ref OFUndefinedKeyException. Index: src/OFObject+KeyValueCoding.m ================================================================== --- src/OFObject+KeyValueCoding.m +++ src/OFObject+KeyValueCoding.m @@ -19,13 +19,14 @@ #include #import "OFObject.h" #import "OFObject+KeyValueCoding.h" +#import "OFArray.h" #import "OFMethodSignature.h" -#import "OFString.h" #import "OFNumber.h" +#import "OFString.h" #import "OFInvalidArgumentException.h" #import "OFOutOfMemoryException.h" #import "OFUndefinedKeyException.h" @@ -32,21 +33,24 @@ int _OFObject_KeyValueCoding_reference; @implementation OFObject (KeyValueCoding) - (id)valueForKey: (OFString *)key { + void *pool = objc_autoreleasePoolPush(); SEL selector = sel_registerName(key.UTF8String); OFMethodSignature *methodSignature = [self methodSignatureForSelector: selector]; id ret; if (methodSignature == nil) { size_t keyLength; char *name; - if ((keyLength = key.UTF8StringLength) < 1) + if ((keyLength = key.UTF8StringLength) < 1) { + objc_autoreleasePoolPop(pool); return [self valueForUndefinedKey: key]; + } if ((name = malloc(keyLength + 3)) == NULL) @throw [OFOutOfMemoryException exceptionWithRequestedSize: keyLength + 3]; @@ -62,24 +66,29 @@ free(name); } methodSignature = [self methodSignatureForSelector: selector]; - if (methodSignature == NULL) + if (methodSignature == NULL) { + objc_autoreleasePoolPop(pool); return [self valueForUndefinedKey: key]; + } switch (*methodSignature.methodReturnType) { case '@': case '#': + objc_autoreleasePoolPop(pool); return [self valueForUndefinedKey: key]; } } if (methodSignature.numberOfArguments != 2 || *[methodSignature argumentTypeAtIndex: 0] != '@' || - *[methodSignature argumentTypeAtIndex: 1] != ':') + *[methodSignature argumentTypeAtIndex: 1] != ':') { + objc_autoreleasePoolPop(pool); return [self valueForUndefinedKey: key]; + } switch (*methodSignature.methodReturnType) { case '@': case '#': ret = [self performSelector: selector]; @@ -105,14 +114,34 @@ CASE('Q', unsigned long long, numberWithUnsignedLongLong:) CASE('f', float, numberWithFloat:) CASE('d', double, numberWithDouble:) #undef CASE default: + objc_autoreleasePoolPop(pool); return [self valueForUndefinedKey: key]; } - return ret; + [ret retain]; + + objc_autoreleasePoolPop(pool); + + return [ret autorelease]; +} + +- (id)valueForKeyPath: (OFString *)keyPath +{ + void *pool = objc_autoreleasePoolPush(); + id ret = self; + + for (OFString *key in [keyPath componentsSeparatedByString: @"."]) + ret = [ret valueForKey: key]; + + [ret retain]; + + objc_autoreleasePoolPop(pool); + + return [ret autorelease]; } - (id)valueForUndefinedKey: (OFString *)key { @throw [OFUndefinedKeyException exceptionWithObject: self @@ -120,19 +149,21 @@ } - (void)setValue: (id)value forKey: (OFString *)key { + void *pool = objc_autoreleasePoolPush(); size_t keyLength; char *name; SEL selector; OFMethodSignature *methodSignature; const char *valueType; if ((keyLength = key.UTF8StringLength) < 1) { - [self setValue: value - forUndefinedKey: key]; + objc_autoreleasePoolPop(pool); + [self setValue: value + forUndefinedKey: key]; return; } if ((name = malloc(keyLength + 5)) == NULL) @throw [OFOutOfMemoryException @@ -155,18 +186,20 @@ if (methodSignature == nil || methodSignature.numberOfArguments != 3 || *methodSignature.methodReturnType != 'v' || *[methodSignature argumentTypeAtIndex: 0] != '@' || *[methodSignature argumentTypeAtIndex: 1] != ':') { - [self setValue: value - forUndefinedKey: key]; + objc_autoreleasePoolPop(pool); + [self setValue: value + forUndefinedKey: key]; return; } valueType = [methodSignature argumentTypeAtIndex: 2]; if (*valueType != '@' && *valueType != '#' && value == nil) { + objc_autoreleasePoolPop(pool); [self setNilValueForKey: key]; return; } switch (*valueType) { @@ -200,14 +233,37 @@ CASE('Q', unsigned long long, unsignedLongLongValue) CASE('f', float, floatValue) CASE('d', double, doubleValue) #undef CASE default: - [self setValue: value - forUndefinedKey: key]; + objc_autoreleasePoolPop(pool); + [self setValue: value + forUndefinedKey: key]; return; } + + objc_autoreleasePoolPop(pool); +} + +- (void)setValue: (id)value + forKeyPath: (OFString *)keyPath +{ + void *pool = objc_autoreleasePoolPush(); + OFArray *keys = [keyPath componentsSeparatedByString: @"."]; + size_t keysCount = keys.count; + id object = self; + size_t i = 0; + + for (OFString *key in keys) { + if (++i == keysCount) + [object setValue: value + forKey: key]; + else + object = [object valueForKey: key]; + } + + objc_autoreleasePoolPop(pool); } - (void)setValue: (id)value forUndefinedKey: (OFString *)key { Index: src/OFSet.h ================================================================== --- src/OFSet.h +++ src/OFSet.h @@ -215,14 +215,13 @@ - (bool)containsObject: (ObjectType)object; /*! * @brief Returns the value for the specified key * - * If the key starts with an `@`, the `@` is stripped and - * `[super valueForKey:]` is called. - * If the key does not start with an `@`, a new set with the value for the - * specified key for each object is returned. + * A new set with the value for the specified key for each object is returned. + * + * The special key `@count` can be used to retrieve the count as an OFNumber. * * @note Unlike with @ref OFArray, any nil values are removed! * * @param key The key of the value to return * @return The value for the specified key @@ -230,21 +229,18 @@ - (nullable id)valueForKey: (OFString *)key; /*! * @brief Set the value for the specified key * - * If the key starts with an `@`, the `@` is stripped and - * `[super setValue:forKey:]` is called. - * If the key does not start with an `@`, @ref setValue:forKey: is called for - * each object. + * @ref setValue:forKey: is called for each object. * * @note A @ref OFNull value is translated to nil! * * @param value The value for the specified key * @param key The key of the value to set */ -- (void)setValue: (id)value +- (void)setValue: (nullable id)value forKey: (OFString *)key; #ifdef OF_HAVE_BLOCKS /*! * @brief Executes a block for each object in the set. Index: src/OFSet.m ================================================================== --- src/OFSet.m +++ src/OFSet.m @@ -214,20 +214,12 @@ - (id)valueForKey: (OFString *)key { id ret; - if ([key hasPrefix: @"@"]) { - void *pool = objc_autoreleasePoolPush(); - - key = [key substringWithRange: of_range(1, key.length - 1)]; - ret = [[super valueForKey: key] retain]; - - objc_autoreleasePoolPop(pool); - - return [ret autorelease]; - } + if ([key isEqual: @"@count"]) + return [super valueForKey: @"count"]; ret = [OFMutableSet setWithCapacity: self.count]; for (id object in self) { id value = [object valueForKey: key]; @@ -242,21 +234,10 @@ } - (void)setValue: (id)value forKey: (OFString *)key { - if ([key hasPrefix: @"@"]) { - void *pool = objc_autoreleasePoolPush(); - - key = [key substringWithRange: of_range(1, key.length - 1)]; - [super setValue: value - forKey: key]; - - objc_autoreleasePoolPop(pool); - return; - } - for (id object in self) [object setValue: value forKey: key]; } Index: tests/OFObjectTests.m ================================================================== --- tests/OFObjectTests.m +++ tests/OFObjectTests.m @@ -248,8 +248,22 @@ EXPECT_EXCEPTION(@"Catch -[setValue:forKey:] with nil key for scalar", OFInvalidArgumentException, [m setValue: (id _Nonnull)nil forKey: @"intValue"]) + TEST(@"-[valueForKeyPath:]", + (m = [[[MyObj alloc] init] autorelease]) && + (m.objectValue = [[[MyObj alloc] init] autorelease]) && + R([m.objectValue + setObjectValue: [[[MyObj alloc] init] autorelease]]) && + R([[m.objectValue objectValue] setDoubleValue: 0.5]) && + [[m valueForKeyPath: @"objectValue.objectValue.doubleValue"] + doubleValue] == 0.5) + + TEST(@"[-setValue:forKeyPath:]", + R([m setValue: [OFNumber numberWithDouble: 0.75] + forKeyPath: @"objectValue.objectValue.doubleValue"]) && + [[[m objectValue] objectValue] doubleValue] == 0.75) + [pool drain]; } @end