如何在 Objective-C 中实现 Swift 中的 defer 一样的功能

时间:2019-04-18
本文章向大家介绍如何在 Objective-C 中实现 Swift 中的 defer 一样的功能,主要包括如何在 Objective-C 中实现 Swift 中的 defer 一样的功能使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

今天要介绍的是 XZKit 框架的基础模块 XZKitConstants 模块提供的一个小功能 defer 宏。

什么是 defer ?

defer 是一种延迟执行机制,就是希望某一段代码能在前面写,但是能够在后面(比如作用域末尾)执行。最在 iOS 开发中,会用到这样一些成对使用的函数,比如 UIGraphicsBeginImageContextUIGraphicsEndImageContext 以及其它很多 CGCF 函数,再如数据库打开后需要关闭。如果这些成对出现的操作之间,业务逻辑比较长,或者需要多次返回,那么就很容易遗忘或者重复写多次,因此编译器提供了清理函数的功能 __attribute__((cleanup(clean_func))) ,而本文即将介绍的 defer 也是基于此功能实现的。

安装

推荐使用 CocoaPods 安装到项目中:

pod 'XZKit/XZKitConstants'

XZKitConstantsXZKit 框架的基础模块,非常轻量级,只有一些常用函数和类目。

效果

原理分析

每次 defer 使用,实际上是在作用域定义了一个局部变量,并在变量上捆绑 __attribute__((cleanup(clean_func))) 函数(block 为函数的参数)。因为局部变量是栈结构,先进后出,最先使用 defer 定义的变量最后出栈,绑定的 cleanup 函数也是最后执行。

代码示例

有了 defer 功能,于是再也不同担心关闭方法没调用了,不行,我要绘制一张纯色图片爽一下。

- (nullable UIImage *)imageByDrawingWithFillColor:(nonnull UIColor *)fillColor imageSize:(CGSize)imageSize
                                      borderWidth:(CGFloat)borderWidth borderColor:(nonnull UIColor *)borderColor
                                     cornerRadius:(CGFloat)cornerRadius roundCorners:(UIRectCorner)roundCorners {
    CGRect imageRect = CGRectMake(0, 0, imageSize.width, imageSize.height);
    CGRect innerRect = CGRectInset(imageRect, borderWidth * 0.5, borderWidth * 0.5);
    
    UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
    defer(^{
        UIGraphicsEndImageContext();
    });
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    if (context == nil) {
        // UIGraphicsEndImageContext();
        return nil;
    }
    
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:innerRect byRoundingCorners:roundCorners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];;
    
    CGContextAddPath(context, path.CGPath);
    CGContextSetFillColorWithColor(context, fillColor.CGColor);
    CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
    CGContextSetShouldAntialias(context, cornerRadius > 0);
    CGContextSetLineWidth(context, borderWidth);
    CGContextDrawPath(context, kCGPathFillStroke);
    
    // UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    // UIGraphicsEndImageContext();
    // return image;
    
    return UIGraphicsGetImageFromCurrentImageContext();
}