📜 ⬆️ ⬇️

10 obscure Objective-C features

I welcome dear habrazhiteli!

Objective-C is a language with a rich runtime, but this article will not <objc/runtime.h> on the contents of the header <objc/runtime.h> , but on some of the features of the language itself, which many developers don’t guess. Yes, you stumble upon them, reading the documentation, you note to yourself “hmm, I wonder, you have to dig a little”, but they usually quickly fly out of my head. And novice developers often even read the documentation diagonally.

In this article, I have gathered 10 amazing in my opinion properties of the Objective-C language. Some properties are self-evident, some are far from such. For the use of some in the combat code, you have to beat your hands, while others are able to help in optimizing critical code points and in debugging. At the end of the article there is a link to the source code, which shows all these features with an example.
')
So, let me begin with the most "tasty" in my opinion: anonymous methods.

1. Unnamed methods



The method name is optional if it has arguments. Often there are methods of the type - (void)setSize:(CGFloat)x :(CGFloat)y , but this can be brought to the absolute:

 @interface TestObject : NSObject + (id):(int)value; - (void):(int)a; - (void):(int)a :(int)b; @end // ... TestObject *obj = [TestObject :2]; [obj :4]; [obj :5 :7]; 


Fun and iron selectors for such methods: @selector(:) and @selector(::) .

Recommendations for use: for research purposes only.

2. The new syntax applies to any objects.


Square brackets to access elements of an array or dictionary can be used with your own objects. To do this, declare the following methods.

To access by index:
 - (id)objectAtIndexedSubscript:(NSUInteger)index; - (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)index; 


For key access:
 - (id)objectForKeyedSubscript:(id)key; - (void)setObject:(id)obj forKeyedSubscript:(id)key; 


We use:
 id a = obj[1]; obj[@"key"] = a; 


Recommendations for use: sometimes possible, but only if your class is not a collection. If it is, it is better to inherit from the available ones.

3. Implicit @property


The @property ad in the header actually announces only the getter and mutator for some field. If you declare them directly, nothing will change:

 - (int)value; - (void)setValue:(int)newValue; obj.value = 2; int i = obj.value; 


Also implicit properties are any functions that take no arguments but return a value:

 NSArray *a = @[@1, @2, @3]; NSInteger c = a.count; 


And also - functions that begin with the name “set”, which return nothing but take one argument:

 @interface TestObject : NSObject - (void)setTitle:(NSString *)title; @end; //... TestObject *obj = [TestObject new]; obj.title = @"simple object"; 


Recommendations for use: the @property ad looks much better, and for this it was introduced. Properties are best accessed through " . ", But conventional methods are best called via " [] ". Otherwise, the readability of the code starts to suffer.

4. Manual allocation of memory for an object without alloc


Objects can be created using the good old calloc function, then specifying isa manually. In principle, this is just a replacement for alloc , and init can be sent later.

 //  ,   void *newObject = calloc(1, class_getInstanceSize([TestObject class])); //  isa     Class *c = (Class *)newObject; c[0] = [TestObject class]; //  __bridge_transfer-      ARC -   obj = (__bridge_transfer TestObject *)newObject; //  init -  ! obj = [obj init]; 


Recommendations for use: it is recommended with extreme caution. One use is to allocate memory at once for a large array of objects (as AlexChernyy once told on CocoaHeads ).

UPD: Thanks to the Trahman user, it was found that this approach does not work on iOS x64. With his help, a portable solution was found:

 object_setClass((__bridge id)newObject, [TestObject class]); 


5. Print the current author pool


ObjC has “hidden” functions that can be accessed using extern :

 extern void _objc_autoreleasePoolPrint(void); 


After calling this function, the contents of the current authorization pool will be displayed in the console, approximately like this:

 objc[26573]: ############## objc[26573]: AUTORELEASE POOLS for thread 0x7fff72fb0310 objc[26573]: 9 releases pending. objc[26573]: [0x100804000] ................ PAGE (hot) (cold) objc[26573]: [0x100804038] ################ POOL 0x100804038 objc[26573]: [0x100804040] 0x100204500 TestObject objc[26573]: [0x100804048] 0x100102fc0 __NSDictionaryM objc[26573]: [0x100804050] 0x1007000b0 __NSArrayI objc[26573]: [0x100804058] 0x1006000a0 __NSCFString objc[26573]: [0x100804060] 0x100600250 NSMethodSignature objc[26573]: [0x100804068] 0x100600290 NSInvocation objc[26573]: [0x100804070] 0x100600530 __NSCFString objc[26573]: [0x100804078] 0x100600650 __NSArrayI objc[26573]: ############## 


This is very useful for debugging. You can search for other useful things here: www.opensource.apple.com/source/objc4/objc4-551.1

Recommendations for use: in debug - please.

6. Direct access to the synthesized @property values


As mentioned above, @property only generates getter and mutator signatures. But if the property is synthesized (via @synthesize or by default), then ivar is also generated:

 @property NSMutableDictionary *dict; - (void)resetDict { _dict = nil; } 


Recommendations for use: access to the synthesized ivar directly can sometimes be justified, but it does not cause the getter and the mutator, which can lead to undesirable effects.

7. Access to public Ivars as in structures


Despite the obviousness of this feature, many developers for some reason do not know about it. Knowledge of this may be useful for resolving name conflicts.

 @interface TestObject : NSObject { @public int field; } @implementation TestObject - (void)updateWithField:(int)field { self->field = field; } @end // ... TestObject *obj = [TestObject new]; obj->field = 200; 


Recommendations for use: in ObjC it is better to use @property for such purposes.

8. instancetype


ObjC has a great id type, which is essentially NSObject * , that is, the most basic type for objects that any other object can lead to. Convenient, but in some cases problems may arise. For example:

 [[MyClass sharedInstance] count]; 


If sharedInstance returns an id , the code will be collected without warning even if there is no count method in MyClass . If sharedInstance returns an instancetype , then the Vorning will still appear, because the compiler clearly understands that an object is returned from the class of which sharedInstance is called.

Recommendations for use: relevant in methods of type init/new/copy , etc.

9. Proxying: forwardingTargetForSelector: and forwardInvocation:


OOP is peculiar to the introduction of additional levels of abstraction for some problems. For example, sometimes you need to make a proxy for another object from an object. The following methods will always help us in this.

If in our object implementation for some selector is not found, the following message will be sent to it:

 - (id)forwardingTargetForSelector:(SEL)aSelector { if ([_dict respondsToSelector:aSelector]) { return _dict; } return [super forwardingTargetForSelector:aSelector]; } 


If the object we are proxying responds to the selector, then let it react to it. Otherwise, all the same will have to throw Exception.

If we want not only to transfer the selector to another object, but also to change the selector itself, we can use the following pair of functions.

First, we must return the signature of the existing method of the proxied object on the request for the signature of an unknown method:

 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *sig = [super methodSignatureForSelector:aSelector]; if ([NSStringFromSelector(aSelector) isEqualToString:@"allDamnKeys"]) { sig = [_dict methodSignatureForSelector:@selector(allKeys)]; } return sig; } 


Then we redirect the call and change the name of the selector:

 - (void)forwardInvocation:(NSInvocation *)anInvocation { if ([NSStringFromSelector(anInvocation.selector) isEqualToString:@"allDamnKeys"]) { anInvocation.selector = @selector(allKeys); [anInvocation invokeWithTarget:_dict]; } } 


Recommendations for use: a very convenient mechanism for implementing proxy objects. Perhaps someone will find other uses. The main thing is not to overdo it: the code should be readable and easy to understand.

10. NSFastEnumeration


Well, in conclusion, another interesting feature of ObjC: for..in cycles. They are supported by all default collections, but we can support as well. To do this, you need to support the NSFastEnumeration protocol, or rather, define the countByEnumeratingWithState:objects:count: method countByEnumeratingWithState:objects:count: but not everything is so simple! Here is the signature of this method:

 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len 


This method will be called every time when runtime wants to receive from us a new portion of objects. We have to write them either into the provided buffer (its len size) or select our own. A pointer to this buffer should be placed in the state->itemsPtr , and the number of objects in it should be returned from the function. Just do not forget that (in the documentation there is no) the field state->mutationsPtr should not be empty. If this is not done, then we get an unexpected SEGFAULT . But in the state->state field, you can write anything, but the best thing is to record the number of elements already sent. If there is nothing to give, you need to return zero.

Here is my example of implementing this function:

 - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len { if (state->state >= _value) { return 0; } NSUInteger itemsToGive = MIN(len, _value - state->state); for (NSUInteger i = 0; i < itemsToGive; ++i) { buffer[i] = @(_values[i + state->state]); } state->itemsPtr = buffer; state->mutationsPtr = &state->extra[0]; state->state += itemsToGive; return itemsToGive; } 


Now you can use:

 for (NSNumber *n in obj) { NSLog(@"n = %@", n); } 


Recommendations for use: it can be useful to simplify working with custom data structures, for example, with trees and related lists.

Conclusion


The complete project source code is available on github . If you, dear reader, have something to add - do not hesitate and write in the comments.

Good luck to everyone in the development and may the Clang Analyzer be with you!

Source: https://habr.com/ru/post/210672/


All Articles