Objective-C traps and defects, objective-c traps

Source: Internet
Author: User

Objective-C traps and defects, objective-c traps

Objective-C is a powerful and useful language, but it is also a little dangerous. This topic is inspired by an article on the C ++ trap to talk about the traps in Objective-C and Cocoa.

Introduction

I will use the same definition as Horstmann: traps are code that can be compiled, linked, and run, but will not be executed as expected. He provided an example where this code is also problematic in Objective-C and in C ++:

<span style="font-family:Arial;font-size:14px;">if (-0.5 <= x <= 0.5) return 0;</span>

A superficial reading of this Code may think that it is used to check whether x is in the range [-0.5, 0.5. But this is not the case. On the contrary, this comparison will be calculated as follows:

<span style="font-family:Arial;font-size:14px;">if ((-0.5 <= x) <= 0.5)</span>

In C, the value of a comparison expression is an integer, either 0 or 1. This is left behind when C does not have a built-in boolean type. Therefore, when x is compared with 0.5, the result is 0 or 1, rather than the value of x. In fact, the second comparison is like a strange negative operator. That is to say, the content of this if statement is executed only when x is smaller than-0.5.

Nil comparison

Objective-C is quite different, because sending messages to nil does not happen, but simply returns 0. Basically, in every language you may encounter, the same thing is either forbidden by the type system or a runtime error. This is both an advantage and a disadvantage. In view of the topic of this article, we will pay attention to the shortcomings.

First, let's look at an equality test:

<span style="font-family:Arial;font-size:14px;">[nil isEqual: @"string"]</span>

Sending messages to nil always returns 0, which is equivalent to NO. This is exactly the right answer, so we seem to have a good start! However, let's look at this:

<span style="font-family:Arial;font-size:14px;">[nil isEqual: nil]</span>

This also returns NO. It does not matter even if the parameters are identical. What is the value of the parameter is not important at all, because 0 is always returned for messages sent to nil. Therefore, judging by isEqual:, nil will never be equivalent to anything, including itself. This is correct in most cases, but not always.

Finally, consider another order of comparison with nil:

<span style="font-family:Arial;font-size:14px;">[@"string" isEqual: nil]</span>

What will happen? Okay, we are not sure. It may return NO, throw an exception, or crash. It is a bad idea to pass nil to a method that does not explicitly inform us that nil is acceptable. Also, isEqual: does not indicate that it can accept nil.

Many Cocoa classes contain a compare: method. This method accepts another object of the same class as the parameter and returns one of NSOrderedAscending, NSOrderedSame, and NSOrderedDescending, which indicates that the value is smaller than, equal to, or greater than the value.

What will happen if we pass nil to compare?

<span style="font-family:Arial;font-size:14px;">[nil compare: nil]</span>

This will return 0, which is exactly the same as NSOrderedSame. Unlike isEqual:, compare: considers that nil and nil are the same. Great! However:

<span style="font-family:Arial;font-size:14px;">[nil compare: @"string"]</span>

In this case, NSOrderedSame is returned, which is obviously the incorrect answer. Compare: The nil and everything are considered equal.

Finally, like isEqual:, passing nil as a parameter to it is also a bad note:

<span style="font-family:Arial;font-size:14px;">[@"string" compare: nil]</span>

Note the following when comparing nil. It does not actually work normally. If your code may encounter nil, you 'd better check and process it separately before you perform isEqual: and compare.

Hash Method

You have written a very small class to save some data, and there are many instances of this class that are equal, so you have implemented the isEqual: method, in this way, these instances can be considered equal. Then you start to add these objects to an NSSet, and things are getting strange. This set claims to hold multiple instances when you add only one object. It cannot find the object you just joined. It may even crash or cause memory errors.

This may occur when you only implement isEqual: but do not implement hash. A large number of Cocoa codes require that if the comparison results of two objects are equal, they should have the same hash value. If you only override isEqual:, you violate this requirement. At any time you overwrite isEqual:, and always overwrite the hash at the same time. For more information, see this article to implement the same-sex and hash algorithms ).

Macro

Suppose you are writing some unit tests. One method should return an array containing an object. So you wrote a test to verify it:

<span style="font-family:Arial;font-size:14px;">STAssertEqualObjects([obj method], @[ @"expected" ], @”Didn’t get the expected array”);</span>

The new text syntax is used to keep it short. Pretty good, right?

Now we have another method to return two objects in the array, So we wrote a test for it:

<span style="font-family:Arial;font-size:14px;">STAssertEqualObjects([obj methodTwo], @[ @"expected1", @"expected2" ], @”Didn’t get the expected array”);</span>

Suddenly, the Code cannot be compiled and a bunch of very strange errors are generated. What's going on?

The problem is that STAssertEqualObjects is a macro. Macros are expanded by pre-processors, and the pre-processor is an ancient, rather stupid program that does not know any modern Objective-C syntax or modern C syntax. The Preprocessor splits macro parameters by commas. It is smart enough to know that parentheses can be recursive, so this macro is regarded as three parameters:

<span style="font-family:Arial;font-size:14px;">Macro(a, (b, c), d)</span>

Here, the first parameter is a, the second parameter is (B, c), and the third parameter is d. However, the pre-processor does not know that it needs to perform the same processing on [] and. In the previous macro, the pre-processor sees four parameters:

· [Obj methodTwo]

· @ [@ "Expected1"

· @ "Expected2]

· @ "Didn't get the expected array"

This result is completely code fragment, not only cannot be compiled, but also puzzles the compiler, making it unable to provide understandable diagnostic information. Once you know where the problem is, the solution is simple. The security of iOS apps written in Objective-C is that there is an encryption technology that can prevent decompilation and cracking. If encryption technology is used, these are not problems at all! As long as the text is enclosed in parentheses, The Preprocessor will regard it as a parameter:

<span style="font-family:Arial;font-size:14px;">STAssertEqualObjects([obj methodTwo], (@[ @"expected1", @"expected2" ]), @”Didn’t get the expected array”);</span>

Unit testing is the most common problem I have encountered, but it may suddenly come out of a macro at any time. Objective-C text will become the victim, and C compound literals will also. If you use a comma in a block, although it is rarely encountered, but it is legal, it may also cause problems. You will find that Apple has taken this issue into account in the Block_copy and Block_release macros. The two macros are in/usr/include/Block. h:

<span style="font-family:Arial;font-size:14px;">#define Block_copy(…) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))#define Block_release(…) _Block_release((const void *)(__VA_ARGS__))</span>

In theory, these macros only accept single parameters, but they are declared to accept variable parameters to avoid this problem. Accept... As a parameter, and use _ VA_ARGS _ to refer to the parameter. The "multiple parameters" with Commas are copied to the macro output. You can avoid this problem using the same method, although it is only valid for the last parameter of the multi-parameter macro.

Property Synthesis)

Take a look at the following class:

<span style="font-family:Arial;font-size:14px;">@interface MyClass : NSObject {NSString *_myIvar;}@property (copy) NSString *myIvar;@end@implementation MyClass@synthesize myIvar;@end</span>

No problem, right? The description of ivar and @ synthesize are a bit redundant, but it does not matter.

Unfortunately, this code will silently ignore _ myIvar and synthesize a new variable myIvar with no prefix or underline. If ivar is directly used in your code, its value will be different from the value of the property directly used in the code. Messy!

@ Synthesize the rule for merging variable names is a bit weird. If you specify the variable name through @ synthesize myIvar = _ myIvar;, of course, it uses any name you specify. If you do not specify a variable name, it is combined into a variable with the same name as the attribute name. If you just omit @ synthesize, it will be a variable with the same name and attribute name but with a prefix and underline.

Unless you need to support a 32-bit Mac, your best choice now is to avoid explicitly declaring the corresponding variable for the attribute. Let @ synthesize create the variable, and if you make a wrong name, you will get a good compilation warning, rather than an incomprehensible behavior.

Interrupted system call

Cocoa Code generally adheres to the advanced structure, but sometimes it is very practical to reduce it to process POSIX. For example, the Code writes some data to a file descriptor:

<span style="font-family:Arial;font-size:14px;">int fd;NSData *data = …;const char *cursor = [data bytes];NSUInteger remaining = [data length];while(remaining > 0) {ssize_t result = write(fd, cursor, remaining);if(result < 0){NSLog(@”Failed to write data: %s (%d)”, strerror(errno), errno);return;}remaining -= result;cursor += result;}</span>

However, this may fail. It may fail in a strange way and intermittently. POSIX calls like this can be interrupted by signals. Even harmless signals processed by other places in the application, such as SIGCHLD and SIGINFO, will cause this situation. If you use NSTask or multithreading, SIGCHLD will generate. When the write is interrupted by a signal, it returns-1 and sets errno to EINTR to indicate that the call is interrupted. The above Code treats all errors as fatal and jumps out, although it only needs to be called again. The correct code should be checked separately and the call should be retried:

<span style="font-family:Arial;font-size:14px;">while(remaining > 0) {ssize_t result = write(fd, cursor, remaining);if(result < 0 && errno == EINTR){continue;}else if(result < 0){NSLog(@”Failed to write data: %s (%d)”, strerror(errno), errno);return;}remaining -= result;cursor += result;}</span>

String Length

The same string is expressed in different ways and has different lengths. This is a common but incorrect example:

Write (fd, [string UTF8String], [string length]);

This problem is that when the write requires a number of bytes, NSString calculates the length in units of UTF-16 encoding. These two numbers are equal only when the string contains only ASCII characters (that is why people are lucky to write such error codes so often ). Once a string contains non-ASCII characters, such as accent characters, they are no longer equal. Please always use the same notation to calculate the length of the string you are operating on:

<span style="font-family:Arial;font-size:14px;">const char *cStr = [string UTF8String];write(fd, cStr, strlen(cStr));</span>

Forced conversion to BOOL type

Check the code used to check whether an object pointer is null:

<span style="font-family:Arial;font-size:14px;">- (BOOL)hasObject{return (BOOL)_object;}</span>

Generally, it works normally. However, it has a probability of about 6%. It returns NO if _ object is not nil. What happened?

BOOL, unfortunately, it is not a boolean type. This is its definition:

<span style="font-family:Arial;font-size:14px;">typedef signed char BOOL;</span>

This is another unfortunate problem left over when C does not have a boolean type. Before the appearance of _ Bool in C99, Cocoa defined its own "Boolean" type as signed char, which is an 8-digit integer. When you turn a pointer into an integer, you will get the value of the pointer itself. When you convert the pointer to a small integer, you will get the number value of the low part of the pointer. When the pointer looks like this:

….110011001110000

Convert to BOOL and you will get:

01110000

This value is not 0, that is, it is correctly calculated. So what is the problem? The problem is that if the pointer looks like this:

….110011000000000

Then convert to BOOL to get:

00000000

This value is 0, that is, NO, even if the pointer itself is not nil. Ah!

How often does this happen? The BOOL type has 256 possible values, and NO accounts for only one of them. Therefore, we can simply assume that the probability of its occurrence is 1/256. However, the Objective-C object is aligned when memory is allocated. Generally, it is 16-bit aligned. That is to say, the minimum 4 bits of the pointer are always 0 (in some cases, they are used to mark the pointer). Therefore, after being converted to BOOL, only the 4 bits will change. Then the possibility that all bits are 0 becomes 1/16, that is, about 6%.

The security implementation of this method requires a display comparison with nil:

<span style="font-family:Arial;font-size:14px;">- (BOOL)hasObject{return _object != nil;}</span>

If you want to be clever and make the code hard to read, you can use it twice in a row! Operator .!! The structure is sometimes called a Boolean conversion operator in C language, although it is only a part of its function.

<span style="font-family:Arial;font-size:14px;">- (BOOL)hasObject{return !!_object;}</span>

Last! Generate a value of 1 or 0 based on whether _ object is nil. Second! Convert it to the correct value. If _ object is nil, 1 is generated; otherwise, 0 is generated.

You should stick to it! = Nil version.

Missing method parameters

Suppose you are implementing a data source for the table view. You add this to the method of your class:

<span style="font-family:Arial;font-size:14px;">- (id)tableView:(NSTableView *) objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex{return [dataArray objectAtIndex: rowIndex];}</span>

So I started to run the application, and then NSTableView began to complain that you didn't implement this method. But it's exactly there!

The computer is correct as usual. Computers are your friends.

Seriously, the first parameter is missing. Why can it be compiled in this way?

The reason is that Objective-C allows an empty selector. The method declared above is not a method with a parameter named tableView: objectValueForTableColumn: row: missing. Instead, the tableView: row: method is declared, and its first parameter is objectValueForTableColumn. this is a rather unpleasant method to type a method name, and if you make this error when a compiler cannot prompt you that the method is lost, it may take a long time for you to debug the problem.

Summary

Objective-C and Cocoa have prepared quite a number of traps for careless programmers. The above is just an example. But it is indeed a good list of issues that need attention.

Related Article

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.