iOS开发笔记(十二)— Extension、iOS9Crash、Pod库和CFDictionary相关

前言

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

正文

一、OC的Extensions特性

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

SSPageControllManager+Report.h

关于蓝色框内的代码,有几个疑问:
1、是否可以放在SSPageControllManager+Report.h文件中运行并且访问声明的属性?
2、如果放在SSPageControllManager+Report.m文件呢?
3、这部分代码和SSPageControllManager.h的中的extension有什么区别?

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


如上,新建的是一个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 (nonatomic, strong) NSString *testCategoryStr;

@end

很遗憾,并不能。只有Extension的声明方式,并且在.m文件中引用,编译器才会自动添加_xx的属性。
不过,getter和setter还是会正常创建,所以可以通过下面的方式来“动态添加”属性。

SSPageControllManager.h如下:

@interface SSPageControllManager (SSUtil)

@property (nonatomic, strong) NSDate *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

二、 iOS 9: Caught "CALayerInvalidGeometry", "sublayer with non-finite position [inf inf]"

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

- (UIImage *)captureView:(UIView *)view {
    CGRect rect = view.bounds;
    UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0.0f);
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGAffineTransform transform = CGAffineTransformMake(-1.0, 0.0, 0.0, 1.0, rect.size.width, 0.0);
    CGContextConcatCTM(context,transform);
    [view.layer renderInContext:context];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

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

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

    self.timeLabel.frame = CGRectNull;

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

问题修复:
问题的触发是因为在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, YES, 0.0f);
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGAffineTransform transform = CGAffineTransformMake(-1.0, 0.0, 0.0, 1.0, rect.size.width, 0.0);
    CGContextConcatCTM(context,transform);
    [view.layer renderInContext:context];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

三、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'

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之后,会出现下面的问题:

分析代码逻辑和上面的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

四、CFDictionary的创建

最近对一段CFDictionary的创建代码产生好奇:
CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
kCFTypeDictionaryKeyCallBackskCFTypeDictionaryValueCallBacks是什么?
于是展开来看,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的引用,所以需要两个参数kCFTypeDictionaryKeyCallBackskCFTypeDictionaryValueCallBacks。默认的实现就是在添加时进行CFRetain,在移除时进行CFRelease。

总结

关于Extension已经学习过很久,但是这次的尝试让我重新回顾脑海中对Extension的了解;相比之下,Category是大家注意的重点,甚至连源码层面如何实现的Category都有相关文章。
保持刨坑问底的习惯,遇到问题时尽量去探究更深一层的原因,这样自己的知识层便会慢慢扩展。或许一开始了解的都是很简单的问题,但是随着简单的问题一一解决,对于更难的问题就可以综合已经学会的基础知识作为支撑尝试解决,久而久之就能触类旁通。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容