Welcome to thumbs up comments, feel useful friends can pay attention to the author’s public number iOS growth refers to north, continue to update

Recently a question was raised by a member of the technical group, why does the following code print 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
Copy the code

In this regard, the author has carried on some research, hoping to explain this problem.

@ {}What is it?

One of the possibilities for this data result, I think

@ {@"a": @1The @"a": @2}
Copy the code

Is itself a dictionary with key A and value 1.

Pass the test code as follows:

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

Find that it is itself a dictionary with key A and value 1.

So what exactly is @{}? How does it actually work? What is his method of distribution?

The experimental steps

Based on NSDictionary pseudocode that we found on the Internet, anyway, when we create a dictionary, it will eventually execute, right

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

So if we hook on this method, we know what objects and keys are passed in during initialization. But, unfortunately, there is no hook.

Is there something wrong with what I’m doing?

I find that this step is not performed at all when using @{}? Is it anything else?

Then the author select breakpoint by adding symbols + [NSDictionary dictionaryWithObjects: forKeys: count:] found that when we assign a value, its symbol breakpoint will live.

Do we call this method when we create a dictionary with @{}? Worth a try?

By hook the dictionary method, we make an acceptance in the classification, and when the system calls, we hang a 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];
}
Copy the code
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
Copy the code

And wait until the result below, when we initialize the Settings, the values passed in are already in the code, but the result is not

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

+ (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]; }Copy the code

guess

There are only two possible changes to the data:

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

As the author of the above two problems have no way to break, if the reader greatly has a way, I hope the reader greatly try. I made my own wild guess — aka a shot in the dark — based on the code for both methods

Unfortunately, they haven’t changed. I don’t see any method in the code that can select objects and keys.

CFBasicHashAddValue and CFBasicHashSetValue

It doesn’t seem to be a problem with its initialization method, and then I compare the dictionary’s setObject:forKey implementation. There are two ways to find out:

CF_PRIVATE Boolean CFBasicHashAddValue(CFBasicHashRef ht, uintptr_t stack_key, uintptr_t stack_value) {··· CFBasicHashBucket BKT = __CFBasicHashFindBucket(ht, stack_key);if (0 < bkt.count) {
        ht->bits.mutations++;
        if (ht->bits.counts_offset && bkt.count < LONG_MAX) { // if not yet as large as a CFIndex can be... otherwise clamp and do nothing
            __CFBasicHashIncSlotCount(ht, bkt.idx);
            return true; }}else {
        __CFBasicHashAddValue(ht, bkt.idx, 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 < bkt.count) {
        __CFBasicHashReplaceValue(ht, bkt.idx, stack_key, stack_value);
    } else{ __CFBasicHashAddValue(ht, bkt.idx, stack_key, stack_value); }}Copy the code

It feels like victory is near, because the __CFBasicHashReplaceValue method is semantically a replacement. So the essence of this should be CFBasicHashAddValue, when there is a key of the same value, it is not added again, and it also explains that the end result is set to the previous value rather than the value set later.

You can also find the values below, feel free to leave your answers in the comments section.

[NSDictionary dictionaryWithObjects:@[@1The @2The @3The @4The @5The @6The @7The @8The @9The @0] forKeys:@[@"a".@"b".@"a".@"b".@"a".@"a".@"b".@"b".@"a".@"b"]]
Copy the code

other

In the hook dictionary itself dictionaryWithObjects: forKeys: count: When, we need to be careful that the breakpoint time, including but not limited to the status bar of the system will eventually be saved in a dictionary, the timing of the saving is when the project is running, preferably in NSDictionary *dic = @{@”a”:@1, @”a”:@2}; Before you hang up the breakpoints, and then let go dictionaryWithObjects: forKeys: count: the breakpoint.


If you have any questions, comments or feedback, please feel free to contact me. If you wish, you can spread the word by sharing this article.

Thank you for reading this! 🚀