When Nsdictionary met Nil

Source: Internet
Author: User
Tags allkeys

Demo Project: nsdictionary-nilsafe

Problem

People who believe in developing IOS apps with objective-c are not unfamiliar with the following crash:

    • * * *-[__nsplaceholderdictionary InitWithObjects:forKeys:count:]: Attempt to Insert Nil object from objects[1]
    • * * * Setobjectforkey:key cannot be nil
    • * * Setobjectforkey:object cannot be nil

 " in >objective-c; nsdictionary Span class= "Apple-converted-space" >  is not supported   Nil   as key or value. But there will always be some places that occasionally go to   nsdictionary   insert   Nil  value. In our project development process, there are two very common scenarios:

    1. The Event log (button click or page impression), such as:
log:SOME_PAGE_IMPRESSION_EVENT eventData:@{    @"some_value": someObject.someValue,}];www.90168.org
    1. When sending an API request, such as:
NSDictionary *params = @{      @"some_key": someValue,};[[APIClient sharedClient] post:someURL params:params callback:callback];

Initially, many of the following fragments exist in our code:

log:SOME_PAGE_IMPRESSION_EVENT eventData:@{    @"some_value": someObject.someValue ?: @"",}];
NSDictionary *params = @{      @"some_key": someValue ?: @"",};

Or:

NSMutableDictionary *params = [NSMutableDictionary dictionary];  if (someValue) {      params[@"some_key"] = someValue;}

There are several disadvantages to doing this:

    1. Too many redundant code
    2. Accidentally forget to check nil, some corner case only live crash will be found on the line
    3. Most of our APIs are in JSON format, so a nil value Whether it is an empty string or not, is semantically incorrect, and may even cause some strange server bugs

so we want   nsdictionary   is used like this:

    1. nil no crash when inserted.
    2. nil The key that it corresponds to after inserting does exist and can fetch the value (NSNull)
    3. is converted to NULL when it is serialize into JSON.
    4. Let's get NSNull closer nil , can eat any way not crash
Test Cases

This task is well suited for test-driven development, so you can simply translate the requirements from the previous section into the following test cases:

- (void) Testliteral {ID nilval =NilID Nilkey =NilID Nonnilkey =@ "Non-nil-key";ID nonnilval =@ "Non-nil-val";Nsdictionary *dict = @{nonnilkey:nilval, Nilkey:nonnilval,}; Xctassertequalobjects ([Dict AllKeys], @[nonnilkey]); Xctassertnothrow ([dict objectforkey:nonnilkey]);ID val = Dict[nonnilkey]; Xctassertequalobjects (Val, [NSNull null]); Xctassertnothrow ([val length]); Xctassertnothrow ([Val Count]); Xctassertnothrow ([Val anyobject]); Xctassertnothrow ([Val intvalue]); Xctassertnothrow ([Val integervalue]);} - (void) Testkeyedsubscript {Nsmutabledictionary *dict = [Nsmutabledictionary dictionary];ID nilval =NilID Nilkey =NilID Nonnilkey =@ "Non-nil-key";ID nonnilval =@ "Non-nil-val"; Dict[nonnilkey] = Nilval; Dict[nilkey] = Nonnilval; Xctassertequalobjects ([Dict AllKeys], @[nonnilkey]); Xctassertnothrow ([dict objectforkey:nonnilkey]);} - (void) Testsetobject {Nsmutabledictionary *dict = [Nsmutabledictionary dictionary];ID nilval =NilID Nilkey =NilID Nonnilkey =@ "Non-nil-key";ID nonnilval =@ "Non-nil-val"; [Dict Setobject:nilval Forkey:nonnilkey]; [Dict Setobject:nonnilval Forkey:nilkey]; Xctassertequalobjects ([Dict AllKeys], @[nonnilkey]); Xctassertnothrow ([dict objectforkey:nonnilkey]);} - (void) Testarchive {ID nilval =NilID Nilkey =NilID Nonnilkey =@ "Non-nil-key";ID nonnilval =@ "Non-nil-val";Nsdictionary *dict = @{nonnilkey:nilval, Nilkey:nonnilval, };  NSData *data = [Nskeyedarchiver archiveddatawithrootobject:dict];Nsdictionary *dict2 = [Nskeyedunarchiver unarchiveobjectwithdata:data]; Xctassertequalobjects ([Dict2 AllKeys], @[nonnilkey]); Xctassertnothrow ([Dict2 Objectforkey:nonnilkey]);}- (void) Testjson {id nilval = NIL; id nilkey = NIL; id nonnilkey = @ "Non-nil-key"; id nonnilval = @ "Non-nil-val"; nsdictionary *dict = @{nonnilkey:nilval, Nilkey:nonnilval,}; nsdata *data = [nsjsonserialization datawithjsonobject:dict options:0 error:null"; nsstring *jsonstring = [[nsstring Alloc] InitWithData:data Encoding:nsutf8stringencoding]; nsstring *expectedstring = @ "{\" non-nil-key\ ": null}"; Xctassertequalobjects (jsonstring, expectedstring);}           

The above code can be found in the demo project, before the transformation, all case should be fail, the purpose of the transformation is to let them all pass.

Method swizzling

According to crash Log,dictionary there are three main entrances to the nil object:

    1. when the literal Initializes a dictionary, the   is invoked; DictionaryWithObjects:forKeys:count:
    2. directly calls   Setobject:forkey  
    3. when the value is assigned by subscript,   is called; setobject:forkeyedsubscript:

So you can pass the method swizzling, the four methods (also initWithObjects:forKeys:count: , although not found where there is a call to it) to replace their own methods, when the key is nil, when the value is nil, replace with NSNull re-insert.

This setObject:forKey method is actually replaced by the method because it is implemented through class cluster __NSDictionaryM .

Take dictionaryWithObjects:forKeys:count: For example:

+ (Instancetype) Gl_dictionarywithobjects: (const id []) objects Forkeys: ( Const id<nscopying> []) Keys count: (Nsuinteger) CNT {id SAFEOBJECTS[CNT]; id safekeys[cnt]; Nsuinteger j = 0; for (Nsuinteger i = 0; i < cnt; i++) {id key = Keys[i]; id obj = objects[i]; if (!key) {continue;} if (!obj) {obj = [NSNull null];} safekeys[j] = key; Safeobjects[j] = obj; j + +;} return [self gl_dictionarywithobjects:safeobjects ForKeys: Safekeys count:j];               

See the GitHub source file for full code.

After this category is introduced, all test cases can be passed smoothly.

Safety of NSNull

As modified Nsdictionary, the odds of getting NSNull from dictionary are higher, so we want NSNull to accept all method calls and return nil/0, just like nil.

At first, we used the Extnil in LIBEXTOBJC as the placeholder to make null more secure. Later found that can actually refer to the implementation of Extnil directly swizzle NSNull itself, so that it can accept all method calls:

-(Nsmethodsignature *) Gl_methodsignatureforselector: (SEL) aselector {nsmethodsignature *sig = [Self Gl_methodSignatur Eforselector:aselector];if (SIG) { return sig; } return [nsmethodsignature signaturewithobjctypes: @encode (void)];} www.90168.org-(void) Gl_forwardinvocation: (nsinvocation *) aninvocation {nsuinteger returnlength = [[ Aninvocation Methodsignature] methodreturnlength]; if (!  Returnlength) {//Nothing to does return;}// set return value to all zero bits char buffer[Returnle Ngth]; memset (buffer, 0, returnlength); [Aninvocation setreturnvalue:buffer];}             
Summarize

At this point, we have solved all the problems mentioned in the first section, with a nil safe nsdictionary. This program in the actual project used for more than a year, the effect is good, the only encounter a pit is to write to the nsuserdefaults with NSNull dictionary time will crash: Attempt to insert non-property list object . Of course this is not the problem of the solution itself, the solution is to put dictionary archive or serialize into JSON and then write to the user Defaults, but then again, the complex structure is considered to be removed from the user Defaults.

When Nsdictionary met Nil

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.