宏观捕获自我块

时间:2014-04-29 16:57:35

标签: ios macros self weak-references

我的下面的宏有一个问题,我用来记录各种信息

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

当在中使用此最终宏时会出现问题,它显然会强烈捕获自身,这可能会有问题。

以下是解决方案的一些要求:

  1. 它可以是一个多线宏,您可以在其中定义weakSelf,但这并不能解决它,因为您可以重新定义您创建的__weak指针。
  2. 使用__FILE____PRETTY_FUNCTION__因为它们捕获将捕获超类而不是子类。因此,对于用于创建许多实例的类的抽象,日志记录不区分每个实例。捕获当前类是绝对必要的
  3. 解决方案只需要修改宏或其他一些全局配置选项来修复此问题,而无需添加额外的扩展库

5 个答案:

答案 0 :(得分:2)

<强>更新

现在我看到了什么问题。这个宏应该有效:

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

LOG_CLASS_NAME(self) // typeof self is JEViewController

因为在编译时解析了typeof(* self),所以编译器不需要保留自身实例。这意味着在块内使用这个宏是安全的。

第一个回答

__ PRETTY_FUNCTION __ 怎么样?它打印一个类名和选择器。

NSLog("func: %s", __PRETTY_FUNCTION__); // func: [UIViewController viewDidAppear:]

答案 1 :(得分:1)

也许使用这个: https://github.com/jspahrsummers/libextobjc

#import "EXTScope.h"

/* some code */

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

/* some code */

我使用此解决方案已有一段时间了 - 无需添加weekSelf或其他任何内容。

答案 2 :(得分:0)

街区外:

__weak __typeof(self) weakSelf = self;

在街区内:

__typeof(self) self = weakSelf;

然后当宏使用self时,它实际上使用了块中定义的self。这将与原始的自身相同或为零(所以检查为零)。

如果您启用了GCC_WARN_SAHDOW,则会生成警告。由于很多原因,其中GCC_WARN_SHADOW并不是一个有用的警告。把它关掉。

答案 3 :(得分:0)

我发现你的问题非常有趣,所以我决定在这里工作几个小时,试图找到另一种方法来打印类名和日志中的方法。

我创建了一个小型XCode项目,在这个项目中我测试了你最初发布的MACRO:

  1. 从我的AppDelegate类调用 JELogVerbose
  2. 从超类(SOAAbstract)及其子类(SOAChild1和SOAChild1)调用 JELogVerbose 。 a)调用没有块的方法。 b)使用不会导致保留周期的块调用方法。 c)调用带有导致保留周期的块的方法,因为self具有对块的强引用。
  3. 从子类首先调用 JELogVerbose 到超类。

  4. 我发现在方案1,2,2a,2.b和3中,您的解决方案非常有效。所以在2.b的情况下,不需要在libextobjc中使用 @weakify(self)/ @ strongify(self )。
    但正如您已经指出的那样,在类似于2.c调用 JELogVerbose 的情况下产生警告 在此块中强烈捕获'self'可能会导致保留周期< / EM>


    我最终得到了这个替代方案,基于类通常在分离的文件上定义的事实:

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


    这里的优点是,您无需担心需要从块内部调用JELogVerbose的所有情况下的保留周期。此外,如果项目中的所有类都是在单独的文件中定义的,则可以从 __ FILE __ 信息中获取该类的名称。
    如果您想提供有关邮件记录的确切位置的信息,您还可以使用 __ LINE __

    据我所知,这个版本的MACRO的缺点是,例如,如果你的类在同一个文件中,那么对于类来说,它们不是100%可用的。但我认为你应该只担心你的代码中的那些情况,其中添加你的MACRO将在 中完成捕获'self'强烈地在这个块中可能会导致保留周期 < / strong>。对于这种情况,您可以使用MACRO的第二个替代版本来避免更改您的代码添加@weakify(self)/ @strongify(self)。



    除了替代方案之外,我还发现了Apple的文档([参考])1中的链接,其中他们建议使用一些预处理器标准宏来向您的日志添加上下文信息。在他们之间我找到了我提到的那个以及一些宏/表达式,其中一些你已经知道了:

    1. __ FUNC __
    2. __ LINE __
    3. __ FILE __
    4. __ PRETTY_FUNCTION __
    5. NSStringFromSelector(_cmd)
    6. NSStringFromClass([self class])
    7. [[NSString stringWithUTF8String:__ FILE__] lastPathComponent]
    8. [NSThread callStackSymbols]

答案 4 :(得分:0)

好。那当然是个腌菜。

我没有看到直截了当的解决方案。让我们仔细考虑一下:

现在,您可能拥有一个使用此日志记录宏的大型代码库。这很好,除了它由于在块中引用self而创建的保留周期而产生一些内存泄漏。 (或者,如果它没有造成内存泄漏,则威胁到。)

因此,您只需要确保对self(概念上)的引用很弱。听起来很合理。

让我们看看你的要求:

第一要求

可以是一个多行宏,它定义了对self的__weak引用。你建议这个棘手的部分是它可能重新定义弱引用(可能是在同一代码块中多次使用)。通过将宏包装在do { something(); } while(0)构造中,可以相对容易地解决这个问题。

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

这是一种非常常见的宏观模式。如果你连续两次调用宏,仍然没有重新定义weakSelf变量。但是,您可能会注意到这对于解决您的问题绝对没有任何意义,因为您必须引用self才能获得对它的弱引用。在创建块之前,您实际上必须具有weakSelf引用,然后在块中仅引用弱引用。实际上,通常情况下,您希望对它进行另一个强引用,以便它不会从您的下方移除。像这样:

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

为了解决这个问题,你必须对自己有一个弱引用,或者有一个跨越块创建的宏。 (那真是个诡计。)

第二项要求

我们不能使用这些有用的预处理器标准宏,因为它们不会告诉您运行时发生了什么,而是编译时间(从技术上讲,预处理(?)时间)。您也拒绝了另一个答案(正确),因为它从编译时间(更近,但仍然没有雪茄)提供信息而不是运行时。

不幸的是,我不认为您可以从self获取运行时信息,而不需要在运行时实际引用它。我们只是在编译期间不知道对象的类实际上是什么。所有这些 PRETTY_FUNCTION 技巧都是正确的。当然,正如人们所期望的那样,所有运行时函数都需要引用您想要查找类的对象。

第三项要求

我要把它归结为:我不想改变我的代码库。可以理解的。

虽然,我认为你没有选择。你有点画到角落里。

结论

我认为你没有任何好的选择(它们都需要在宏之外进行更改,即更改代码库):

  1. 通过删除对self的任何引用来创建宏的块安全版本,并始终在块
  2. 中使用它
  3. 创建使用strongSelf的宏的块安全版本,然后无论何时使用它,您还必须在块外创建weakSelf引用,并在块内创建strongSelf(如上例所示) 。这是更多的工作,但它会保留您存在的所有日志记录。
  4. 祝你好运!