iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关

iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关


作者丨落影loyinglin

https://www.jianshu.com/p/97b8912c4124


iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关前言


分享iOS开发中遇到的问题,和相关的一些思考,本次内容包括:Extension、iOS9Crash、Pod库和CFDictionary相关。


iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关正文


一、OC的Extensions特性


先看下图,这是一段Category中的代码:(SSPageControllManager+Report.h文件中)


iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关

SSPageControllManager+Report.h


关于蓝色框内的代码,有几个疑问:


1、是否可以放在SSPageControllManager+Report.h文件中运行并且访问声明的属性?


2、如果放在SSPageControllManager+Report.m文件呢?


3、这部分代码和SSPageControllManager.h的中的extension有什么区别?


在回复上面的疑问之前,我们先回顾下创建Extension的过程:通过Xcode的command+N新建文件,选择Objective-C File,再选择Extension;


iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关


如上,新建的是一个SSPageControllManager+Property.h文件,并且没有生成.m文件。



@interface SSPageControllManager ()
@end


对于疑问1----是否可以在SSPageControllManager+Report.h中写Extension(蓝色框的代码)并且运行时去访问声明的属性?答案是可以的,因为SSPageControllManager+Report.h是一个头文件,在头文件写Extension就是标准生成的Extension(上面的代码块);


对于疑问2----如果想在SSPageControllManager+Report.m中使用Extension,则需要手动实现getter和setter,否则实现时会因为访问不到_xx的属性而crash;


对于疑问3----Extension写在哪里的位置并不重要,核心是在于SSPageControllManager.m中要能引导到这个文件,以便于编译时SSPageControllManager.m能够自动添加对应的_xx属性。


因此,如果我们使用xx+Property.h的Extension来管理属性时,则一定要xx.m的文件中include这个头文件,否则属性无法正常初始化。比如说xx.m不引入xx+Property.h,然后在xx+report.m中去引用xx+Property.h中的属性,则会出现异常。


Extension和Category一个核心的区别,就在于能否在xx.m中去引用对应的头文件。


那么问题来了,如果我在Category的@interface代码块中声明属性,然后在.m引用对应的头文件,是否能够访问testCategoryStr这个属性?



@interface SSPageControllManager (Report)

@property (nonatomicstrongNSString *testCategoryStr;

@end


很遗憾,并不能。只有Extension的声明方式,并且在.m文件中引用,编译器才会自动添加_xx的属性。


不过,getter和setter还是会正常创建,所以可以通过下面的方式来“动态添加”属性。


SSPageControllManager.h如下:



@interface SSPageControllManager (SSUtil)

@property (nonatomicstrongNSDate *ssStoreDate;

@end


SSPageControllManager.m如下:



@implementation SSPageControllManager (SSUtil)

- (NSDate *)ssStoreDate {
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setSsStoreDate:(NSDate *)storeDate {
    objc_setAssociatedObject(self@selector(ssStoreDate), storeDate, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end


Extension

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html


iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关二、 iOS 9: Caught "CALayerInvalidGeometry", "sublayer with non-finite position [inf inf]"


该问题发生在对view进行截图时,截图的代码如下:



- (UIImage *)captureView:(UIView *)view {
    CGRect rect = view.bounds;
    UIGraphicsBeginImageContextWithOptions(rect.size, YES0.0f);
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGAffineTransform transform = CGAffineTransformMake(-1.00.00.01.0, rect.size.width, 0.0);
    CGContextConcatCTM(context,transform);
    [view.layer renderInContext:context];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}


仅在iOS9的时候,会发生CALayerInvalidGeometry的crash。


iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关


在复现的过程发现将width设置为0,并不会触发该问题,需要view的rect为 CGRectNull 时才会触发。



    self.timeLabel.frame = CGRectNull;


这行代码可以复现,且iOS12不会crash,仅在iOS9会crash;


iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关问题修复:


问题的触发是因为在render时,存在某些view的rect为 CGRectNull;那么可以尝试通过遍历视图树,检查是否存在异常view。


CGRectNull判断方法:CGRectIsNull(view.frame)。注意不是CGRectIsNull(view.bounds),通过frame的值来看,可以判断出来:frame = (inf inf; 0 0);


从这里可以看出,为什么前面仅仅设置width=0没有触发crash。


CGRectNull与CGRectZero不同,上面的frame可以看出。


最终修复方案是增加判断方法checkNullRect:(如果业务需要一定返回图片,那么可以返回空,也可以将其frame设置为CGRectZero但是不合理,可能影响其他业务逻辑)



- (BOOL)checkNullRect:(UIView *)view {
    BOOL ret = CGRectIsNull(view.frame);
    for (UIView *subView in view.subviews) {
        ret = ret || [self checkNullRect:subView];
    }
    if (ret) {
        SSLOG_ERROR(@"zero frame, view:%@", view);
    }
    return ret;
}

- (UIImage *)captureView:(UIView *)view {
    if ([self checkNullRect:view]) {
        return nil;
    }
    CGRect rect = view.bounds;
    UIGraphicsBeginImageContextWithOptions(rect.size, YES0.0f);
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGAffineTransform transform = CGAffineTransformMake(-1.00.00.01.0, rect.size.width, 0.0);
    CGContextConcatCTM(context,transform);
    [view.layer renderInContext:context];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}


iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关三、Pod库相关


配置Podfile,执行pod install之后,工程一切正常。


但是当我把LYTest.project的Build Active Architecture Only属性设置为No之后,就出现了异常:


Target 'Pods-LYTest' of project 'Pods' was rejected as an implicit dependency for 'libPods-LYTest.a' because its architectures 'x86_64' didn't contain all required architectures 'i386x86_64'


iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关

Build Active Architecture Only属性


尝试重新pod install,问题仍存在。


通过检查pod库的设置,发现是因为pod库的Build Active Architecture Only属性在debug默认为Yes;  而LYTest.project的设置为No,所以libPods-LYTest.a中缺少了i386的architecture。


手动将pod库的Build Active Architecture Only属性设置为No,问题可以解决。


但是在每次pod install之后,仍需要手动修改Pod库的工程设置,总感觉应该可以通过脚本完成这个过程。


最后在StackOverflow中得到启发:



post_install do |installer_representation|
    installer_representation.project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO'
        end
    end
end


但是上面的代码插入podfile之后,会出现下面的问题:


iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关


分析代码逻辑和上面的error提示,可以发现这里的installer_representation.project修改的应该是project的设置,将其改为installer_representation.pods_project,问题得到解决。



post_install do |installer_representation|
    installer_representation.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO'
        end
    end
end


iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关四、CFDictionary的创建


最近对一段CFDictionary的创建代码产生好奇:


CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);


kCFTypeDictionaryKeyCallBacks和kCFTypeDictionaryValueCallBacks是什么?


于是展开来看,kCFTypeDictionaryKeyCallBacks是5个callback加1个version组成。



typedef struct {
    CFIndex             version;
    CFDictionaryRetainCallBack      retain;
    CFDictionaryReleaseCallBack     release;
    CFDictionaryCopyDescriptionCallBack copyDescription;
    CFDictionaryEqualCallBack       equal;
    CFDictionaryHashCallBack        hash;
CFDictionaryKeyCallBacks;


其中的retain,对应的类是CFDictionaryRetainCallBack;


typedef const void * (*CFDictionaryRetainCallBack)(CFAllocatorRef allocator, const void *value);


到这里,我们能明白这5个方法分别是对key做retain、release、copy,判断等于,hash时会用到的方法,并且我们可以知道如果需要对key被retain时做额外处理,可以按照如下实现:



void *keyRetainCallBack(CFAllocatorRef allocator, const void * value)
{
    id obj = (id)value;
    [obj retain];
    // do something
    return obj;
}


CF是内存是手动管理,而CFDictionary作为容器类,需要知道当key、value被添加到容器时,应该如何处理key、value的引用,所以需要两个参数kCFTypeDictionaryKeyCallBacks和kCFTypeDictionaryValueCallBacks。默认的实现就是在添加时进行CFRetain,在移除时进行CFRelease。


iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关总结


关于Extension已经学习过很久,但是这次的尝试让我重新回顾脑海中对Extension的了解;相比之下,Category是大家注意的重点,甚至连源码层面如何实现的Category都有相关文章。


保持刨坑问底的习惯,遇到问题时尽量去探究更深一层的原因,这样自己的知识层便会慢慢扩展。或许一开始了解的都是很简单的问题,但是随着简单的问题一一解决,对于更难的问题就可以综合已经学会的基础知识作为支撑尝试解决,久而久之就能触类旁通。


【无门槛免费领】

535G超强程序员编程

0基础从入门到精通自学****!

iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关

iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关 iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关

iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关 iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关 iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关

iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关 iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关 iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关

iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关 iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关 iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关

iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关 iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关 iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关 iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关

iOS开发笔记— Extension、iOS9Crash、Pod库和CFDictionary相关万水千山总是情,点个 “在看” 行不行