Macro capturing Self in block
Asked Answered
E

5

7

I have a problem with the below macro which i use to log various bits of information

#define JELogVerbose(fmt, ...)  
DDLogVerbose((@"%@ %@ - " fmt), NSStringFromClass([self class]),
                                NSStringFromSelector(_cmd), ##__VA_ARGS__)

The problem occurs when this final macro is used inside a block, it will obviously strongly capture self which can be problematic.

Here are some requirements for a solution:

  1. It can be a multiline macro where you define weakSelf but that doesn't solve it as you can get a redefinition of the __weak pointer you create.
  2. Using __FILE__ or __PRETTY_FUNCTION__ because they capture will capture the superclass rather than the subclass. So in the case of an abstract that class that is used to create many instances the logging does not differentiate between each instance. Capturing the current class is absolutely necessary
  3. The solution only requires modification of the macro or some other global config option to fix this without the need to add additional extensive libraries
Ezmeralda answered 29/4, 2014 at 16:57 Comment(0)
L
2

UPDATE:

Now I see what's the problem. This macro should work:

#define LOG_CLASS_NAME(obj) typedef typeof(*(obj)) SelfType; \
                            NSLog(@"typeof self is %@", [SelfType class]);

LOG_CLASS_NAME(self) // typeof self is JEViewController

because typeof(*self) is resolved at compile time there is no need for the compiler to retain self instance. That means it is safe to use this macro inside block.

First answer:

How about __PRETTY_FUNCTION__ ? It prints a class name as well as selector.

NSLog("func: %s", __PRETTY_FUNCTION__); // func: [UIViewController viewDidAppear:]
Lardon answered 29/4, 2014 at 21:30 Comment(4)
Unfortunately this always captures the file that its within, so if a superclass has a function that uses this but a message is sent to a subclass, this will print it as coming from the superclass which is not what i am looking for.Ezmeralda
Unfortunately this will capture the superclass rather than the subclassEzmeralda
@Marek Rogosz, I admire your wisdom! Such a challenging question and such an interesting solution!Malacology
You probably want to wrap your solution into ({ … }) to avoid redeclaring SelfType if used several times within the same scope and for overall sanity:)Bordereau
A
1

Maybe use this: https://github.com/jspahrsummers/libextobjc

#import "EXTScope.h"

/* some code */

@weakify(self);
[SomeClass runOnBackgroundCode:^{
    @strongify(self);
    /* do something */
}];

/* some code */

I use this solution for quite some time - no need to add weekSelf or anything else.

Avert answered 29/4, 2014 at 17:32 Comment(1)
Interesting approach, this does let me use the same macros and would solve the problem. I was looking for something that does not require any external config but this could be it. May accept if nothing that directly address the macro comes along.Ezmeralda
N
0

Outside the block:

__weak __typeof(self) weakSelf = self;

Inside the block:

__typeof(self) self = weakSelf;

Then when the macro uses self, it's actually using the self defined within the block. That will either be the same as the original self or nil (so check for nil).

If you have GCC_WARN_SAHDOW on, this will generate a warning. For many reasons, this among them, GCC_WARN_SHADOW isn't a helpful warning. Turn it off.

Numbers answered 29/4, 2014 at 17:35 Comment(4)
As i want to use this macro everywhere and i am looking for a generic solution i am afraid this is not really an option. You can do this when you are in a block and pass self into the Macro but its less than elegantEzmeralda
This is one line for the parent function, then one line for each block.Numbers
I see what you mean but this is being used in a large-ish code base and i would prefer to have zero config over additional requirements. If i was going to make changes to each block to include this additional code and flag, i can just edit the macro so that it accepts self as a parameter and pass in an already __weak ref to sel. That doesn't require me to disable the potentially helpful shadow warning but if i can avoid doing that i would prefer it.Ezmeralda
Good luck. I hope you find an answer that requires no code. However, I do recommend against turning GCC_WARN_SHADOW on in any case. I've found it made my code substantially worse, a real death by a thousand cuts. More pedantic doesn't always mean better. :)Numbers
M
0

I found you question quite interesting so I decided to work on this a couple of hours trying to find an alternative way to print the class name and also the method in your logs.

I created a small XCode project in which I tested your originally posted MACRO under this scenarios:

  1. Calling JELogVerbose from my AppDelegate class.
  2. Calling JELogVerbose from a superclass (SOAAbstract) and its children classes (SOAChild1 and SOAChild1). a) Calling a method without blocks. b) Calling a method with a block that does not cause retain cycles. c) Calling a method with a block that causes a retain cycle since self has a strong reference to the block.
  3. Calling JELogVerbose from a child class casting first to superclass.


I found that in scenarios 1, 2.a, 2.b and 3 your solution works perfectly. So there is no need to use @weakify(self)/@strongify(self) from libextobjc in 2.b cases.
But as you've already pointed, in cases similar to 2.c calling JELogVerbose produces the warning Capturing 'self' strongly in this block is likely to lead to a retain cycle.


I ended up with this alternative, based on the fact the classes are usually defined on separated files:

#define JELogVerbose(fmt, ...) DDLogVerbose((@"%@ %@ - " fmt), [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] stringByDeletingLastPathComponent], NSStringFromSelector(_cmd) ,##__VA_ARGS__)


The advantage here is that you don't need to worry about retain cycles in all cases in which you need to call JELogVerbose from inside a block. Additionally if all classes in your project are defined in separated files you could derive the name of the class from the __FILE__ information.
You could also make use of __LINE__ if you'd like to provide information about the exact point in which the message was logged.

I understand that this version of the MACRO have the disadvantage of not being 100% mappeable to classes in the case in which your classes are in the same file for example. But I consider you should only be worried about those cases in your code in which adding your MACRO would finish in a Capturing 'self' strongly in this block is likely to lead to a retain cycle . For this cases, you could use this second alternative version of the MACRO to avoid changing your code adding @weakify(self) / @strongify(self).



Apart from that alternative I've found a link from Apple's Docs ([reference])1 in which they recommend some preprocessor standard macros provided to add context information to your logs. Between them I found the one I mentioned and some more macros / expressions, some of these that you'd be already aware of:

  1. __func__
  2. __LINE__
  3. __FILE__
  4. __PRETTY_FUNCTION__
  5. NSStringFromSelector(_cmd)
  6. NSStringFromClass([self class])
  7. [[NSString stringWithUTF8String:__FILE__] lastPathComponent]
  8. [NSThread callStackSymbols]
Marjorie answered 6/5, 2014 at 17:46 Comment(1)
Thanks for the answer but as you said " disadvantage of not being 100% mappable to classes in the case in which your classes are in the same file for example" - this means that i cannot use this solution.Ezmeralda
A
0

Well. That certainly is a pickle.

And I don't see straightforward solution to it. Let's think through this a bit:

Right now, you presumably have a large code base where you use this logging macro. It's great, except that it's creating some memory leaks due to the retain cycles created by referring to self in blocks. (Or, if it isn't creating memory leaks, it's threatening to.)

So, you just need to make sure this reference to self (conceptually) is weak. Sounds reasonable enough.

Let's look at your requirements:

First Requirement

Can be a multiline macro which defines a __weak reference to self. You suggest that the tricky part to this is that it could potentially redefine the weak reference (presumably by being used multiple times in the same block of code). This is relatively easily worked around by wrapping the macro in a do { something(); } while(0) construct.

#define JELogVerbose(fmt, ...)
do
{
    __weak __typeof(self) weakSelf = self;  
    DDLogVerbose((@"%@ %@ - " fmt), NSStringFromClass([weakSelf class]),
                                    NSStringFromSelector(_cmd), ##__VA_ARGS__)
} while (0)

Which is a very common macro pattern. And if you call the macro two times in a row, there will still be no redefinition of the weakSelf variable. However, you may notice that this does absolutely nothing to actually solve your issue, as you have to refer to self in order to get a weak reference to it. You actually have to have the weakSelf reference before the block is created, and then in the block ONLY refer to the weak reference. In fact, generally, you want to make another strong reference to it so that it's not removed out of underneath you. Like this:

__weak typeof(self) weakSelf = self;
[someObj block:^{
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    MyLog(strongSelf);
}];

In order to fix that, you'll have to ALREADY have a weak reference to self, or have a macro that spans over the creation of a block. (That'd be quite a trick.)

Second Requirement

We can't use these helpful preprocessor standard macros, because they don't tell you what's happening at runtime, rather compile time (more technically, a preprocess (?) time). You also rejected another answer (correctly) because it gives you the information from compile time (closer, but still no cigar) rather than runtime.

Unfortunately, I don't think you can get the runtime information from self you're looking for without actually referring to it during the runtime. We just don't know during compile time what the class of the object is actually going to be. So all of those PRETTY_FUNCTION tricks are right out. And of course, as one would expect, all the runtime functions require a reference to the object you want to find the class for.

Third Requirement

I'm going to boil this down to: I don't want to change my code base. Understandable.

Although, I don't think you have a choice. You're kinda painted into a corner.

Conclusion

I don't think you have any great options (they all require changes outside of the macro, ie changes to your code base):

  1. Create a block-safe version of the macro by removing any references to self, and always use it in blocks
  2. Create a block-safe version of the macro that uses strongSelf and then whenever you use it, you must also create your weakSelf reference outside the block, and a strongSelf inside the block (as in the example above). This is more work, but it would preserve all your logging as it exists.

Good luck!

Apheliotropic answered 15/5, 2014 at 22:13 Comment(1)
Thanks for a great breakdown of the question, you have really summed up the issue. I fear that as you point out that i have to compromise for something and break some of my requirements.Ezmeralda

© 2022 - 2024 — McMap. All rights reserved.