SoFunction
Updated on 2025-03-10

Explain the syntax sugar @{} in Objective-C in detail

Recently, a group member in the technical group asked a question, which is why the following code prints differently?

NSMutableDictionary *mDic1 = [NSMutableDictionary dictionaryWithDictionary:@{@"a":@1, @"a":@2}];
//'a': 1
NSMutableDictionary *mDic2 = [NSMutableDictionary dictionary];
[mDic2 setObject:@(1) forKey:@"a"];
[mDic2 setObject:@(2) forKey:@"a"];
 //'a': 2

In this regard, the author has studied it a little. Here, I will explain the reasons and briefly describe the experimental steps.

@{} What exactly is it?

One of the possibilities for this data result should be

@{@"a":@1, @"a":@2}

It is itself a dictionary with key a and value of 1.

Pass the test code as follows:

NSDictionary *dic = @{@"a":@1, @"a":@2};
NSLog(@"%@", dic);

It is found that it is a dictionary with key a and value of 1.

So what is @{}? How to operate it in fact? What exactly is his distribution method?

Experimental steps

Based on the pseudo-code of NSDictionary found online, anyway, when we create a dictionary, it will eventually be executed

- (id)initWithObjects:(const id [])objects forKeys:(const id <NSCopying> [])keys count:(NSUInteger)cnt

So if we listen to this method through hook, we will know what are the objects and keys passed in during initialization? But, unfortunately, there was no hook.

Is there something wrong with my approach?

The author finds that this step is not performed at all when using @{}? Is it something else?

Then I chose to break the point by adding symbols +[NSDictionary dictionaryWithObjects:forKeys:count:] It is found that when we assign values, its symbol breakpoint will hang.

Will we call this method when creating a dictionary using @{}? Worth a try?

By hooking the dictionary method, we accept it in the classification, and when the system is called, we hang up the breakpoint

+ (id)xxx_dictionaryWithObjects:(const id [])objects forKeys:(const id <NSCopying> [])keys count:(NSUInteger)cnt{
 for (NSUInteger i = 0; i < cnt; i++) {
 id key = keys[i];
 id obj = objects[i];
 NSLog(@"key = %@", key);
 NSLog(@"obj = %@", obj);
 }
 return [[NSDictionary class] xxx_dictionaryWithObjects:objects forKeys:keys count:cnt];
}

2021-03-30 17:13:40.971674+0800 suspenseRoad[28886:413231] key = a
2021-03-30 17:13:40.971743+0800 suspenseRoad[28886:413231] obj = 1
2021-03-30 17:13:40.971814+0800 suspenseRoad[28886:413231] key = a
2021-03-30 17:13:40.971896+0800 suspenseRoad[28886:413231] obj = 2

And wait for the following result. When we initialize the settings, the passed values ​​have entered into the code, but the result has not been

Continue to explore+[NSDictionary dictionaryWithObjects:forKeys:count:] Method

+ (id)dictionaryWithDictionary:(NSDictionary *)dict
{
 size_t count = [dict count];
 id *objects = NULL;
 id *keys = NULL;

 if (count > 0) {
 objects = malloc(sizeof(id) * count);
 if (UNLIKELY(objects == NULL)) {
  return NULL;
 }
 keys = malloc(sizeof(id) * count);
 if (UNLIKELY(keys == NULL)) {
  free(objects);
  return NULL;
 }
 }
 
 [dict getObjects:objects andKeys:keys];
 id obj = [[self alloc] initWithObjects:objects forKeys:keys count:count];

 if (objects != NULL) {
 free(objects);
 }
 if (keys != NULL) {
 free(keys);
 }

 return [obj autorelease];
}

guess

At this time, there are only two places that may change the data:

 [dict getObjects:objects andKeys:keys];
//or id obj = [[self alloc] initWithObjects:objects forKeys:keys count:count];

Since the author has no way to break the point of the above two problems, if the readers have a solution, I hope the readers will try it out. The author made his own "bold guessing" based on the code of two methods - that is, guessing blindly

Unfortunately, none of them were changed. I don't see any method in the code to traverse the selection of objects and keys.

CFBasicHashAddValue and CFBasicHashSetValue

It seems that it is not a problem with its initialization method, and then the author compared the implementation of setObject:forKey in the dictionary. I found two methods as shown in the question:

CF_PRIVATE Boolean CFBasicHashAddValue(CFBasicHashRef ht, uintptr_t stack_key, uintptr_t stack_value) {
 ···
 CFBasicHashBucket bkt = __CFBasicHashFindBucket(ht, stack_key);
 if (0 < ) {
  ht->++;
  if (ht->bits.counts_offset &&  < LONG_MAX) { // if not yet as large as a CFIndex can be... otherwise clamp and do nothing
   __CFBasicHashIncSlotCount(ht, );
   return true;
  }
 } else {
  __CFBasicHashAddValue(ht, , stack_key, stack_value);
  return true;
 }
 return false;
}

CF_PRIVATE void CFBasicHashSetValue(CFBasicHashRef ht, uintptr_t stack_key, uintptr_t stack_value) {
 ···
 CFBasicHashBucket bkt = __CFBasicHashFindBucket(ht, stack_key);
 if (0 < ) {
  __CFBasicHashReplaceValue(ht, , stack_key, stack_value);
 } else {
  __CFBasicHashAddValue(ht, , stack_key, stack_value);
 }
}

It feels like the victory is not far away, because the __CFBasicHashReplaceValue method is semantically a replacement. Then its essence should be CFBasicHashAddValue . When there is a key of the same value, it will not be added again. It is also explained that the final result is to set the previous value instead of the subsequent value.

You can also get the following value, welcome to write the answer in the comment section.

[NSDictionary dictionaryWithObjects:@[@1,@2,@3,@4,@5,@6,@7,@8,@9,@0] forKeys:@[@"a",@"b", @"a", @"b", @"a", @"a", @"b", @"b", @"a", @"b"]]

other

When the hook dictionary itself is dictionaryWithObjects:forKeys:count:, we need to be careful about the time of breakpoints, including when information such as not limited to the system's status bar will eventually be stored in a dictionary. The time for deposit is when the project is running. It is best to hang a breakpoint before NSDictionary *dic = @{@"a":@1, @"a":@2};, and then release the dictionaryWithObjects:forKeys:count: breakpoint.

This is the article about what syntax sugar @{} in Objective-C is introduced here. For more related Objective-C syntax sugar @{}, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!