前言
分享iOS開發(fā)中遇到的問題,和相關(guān)的一些思考,本次內(nèi)容包括:Extension、iOS9Crash、Pod庫和CFDictionary相關(guān)。
正文
一、OC的Extensions特性
先看下圖,這是一段Category中的代碼:(SSPageControllManager+Report.h文件中)

關(guān)于藍(lán)色框內(nèi)的代碼,有幾個疑問:
1、是否可以放在SSPageControllManager+Report.h文件中運(yùn)行并且訪問聲明的屬性?
2、如果放在SSPageControllManager+Report.m文件呢?
3、這部分代碼和SSPageControllManager.h的中的extension有什么區(qū)別?
在回復(fù)上面的疑問之前,我們先回顧下創(chuàng)建Extension的過程:通過Xcode的command+N新建文件,選擇Objective-C File,再選擇Extension;

如上,新建的是一個SSPageControllManager+Property.h文件,并且沒有生成.m文件。
@interface SSPageControllManager ()
@end
對于疑問1----是否可以在SSPageControllManager+Report.h中寫Extension(藍(lán)色框的代碼)并且運(yùn)行時去訪問聲明的屬性?答案是可以的,因為SSPageControllManager+Report.h是一個頭文件,在頭文件寫Extension就是標(biāo)準(zhǔn)生成的Extension(上面的代碼塊);
對于疑問2----如果想在SSPageControllManager+Report.m中使用Extension,則需要手動實現(xiàn)getter和setter,否則實現(xiàn)時會因為訪問不到_xx的屬性而crash;
對于疑問3----Extension寫在哪里的位置并不重要,核心是在于SSPageControllManager.m中要能引導(dǎo)到這個文件,以便于編譯時SSPageControllManager.m能夠自動添加對應(yīng)的_xx屬性。
因此,如果我們使用xx+Property.h的Extension來管理屬性時,則一定要xx.m的文件中include這個頭文件,否則屬性無法正常初始化。比如說xx.m不引入xx+Property.h,然后在xx+report.m中去引用xx+Property.h中的屬性,則會出現(xiàn)異常。
Extension和Category一個核心的區(qū)別,就在于能否在xx.m中去引用對應(yīng)的頭文件。
那么問題來了,如果我在Category的@interface代碼塊中聲明屬性,然后在.m引用對應(yīng)的頭文件,是否能夠訪問testCategoryStr這個屬性?
@interface SSPageControllManager (Report)
@property (nonatomic, strong) NSString *testCategoryStr;
@end
很遺憾,并不能。只有Extension的聲明方式,并且在.m文件中引用,編譯器才會自動添加_xx的屬性。
不過,getter和setter還是會正常創(chuàng)建,所以可以通過下面的方式來“動態(tài)添加”屬性。
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
二、 iOS 9: Caught "CALayerInvalidGeometry", "sublayer with non-finite position [inf inf]"
該問題發(fā)生在對view進(jìn)行截圖時,截圖的代碼如下:
- (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的時候,會發(fā)生CALayerInvalidGeometry的crash。

在復(fù)現(xiàn)的過程發(fā)現(xiàn)將width設(shè)置為0,并不會觸發(fā)該問題,需要view的rect為 CGRectNull 時才會觸發(fā)。
self.timeLabel.frame = CGRectNull;
這行代碼可以復(fù)現(xiàn),且iOS12不會crash,僅在iOS9會crash;
問題修復(fù):
問題的觸發(fā)是因為在render時,存在某些view的rect為 CGRectNull;那么可以嘗試通過遍歷視圖樹,檢查是否存在異常view。
CGRectNull判斷方法:CGRectIsNull(view.frame)。注意不是CGRectIsNull(view.bounds),通過frame的值來看,可以判斷出來:frame = (inf inf; 0 0);
從這里可以看出,為什么前面僅僅設(shè)置width=0沒有觸發(fā)crash。
CGRectNull與CGRectZero不同,上面的frame可以看出。
最終修復(fù)方案是增加判斷方法checkNullRect:(如果業(yè)務(wù)需要一定返回圖片,那么可以返回空,也可以將其frame設(shè)置為CGRectZero但是不合理,可能影響其他業(yè)務(wù)邏輯)
- (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庫相關(guān)
配置Podfile,執(zhí)行pod install之后,工程一切正常。
但是當(dāng)我把LYTest.project的Build Active Architecture Only屬性設(shè)置為No之后,就出現(xiàn)了異常:
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'

嘗試重新pod install,問題仍存在。
通過檢查pod庫的設(shè)置,發(fā)現(xiàn)是因為pod庫的Build Active Architecture Only屬性在debug默認(rèn)為Yes; 而LYTest.project的設(shè)置為No,所以libPods-LYTest.a中缺少了i386的architecture。
手動將pod庫的Build Active Architecture Only屬性設(shè)置為No,問題可以解決。
但是在每次pod install之后,仍需要手動修改Pod庫的工程設(shè)置,總感覺應(yīng)該可以通過腳本完成這個過程。
最后在StackOverflow中得到啟發(fā):
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之后,會出現(xiàn)下面的問題:

分析代碼邏輯和上面的error提示,可以發(fā)現(xiàn)這里的installer_representation.project修改的應(yīng)該是project的設(shè)置,將其改為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的創(chuàng)建
最近對一段CFDictionary的創(chuàng)建代碼產(chǎn)生好奇:
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,對應(yīng)的類是CFDictionaryRetainCallBack;
typedef const void * (*CFDictionaryRetainCallBack)(CFAllocatorRef allocator, const void *value);
到這里,我們能明白這5個方法分別是對key做retain、release、copy,判斷等于,hash時會用到的方法,并且我們可以知道如果需要對key被retain時做額外處理,可以按照如下實現(xiàn):
void *keyRetainCallBack(CFAllocatorRef allocator, const void * value)
{
id obj = (id)value;
[obj retain];
// do something
return obj;
}
CF是內(nèi)存是手動管理,而CFDictionary作為容器類,需要知道當(dāng)key、value被添加到容器時,應(yīng)該如何處理key、value的引用,所以需要兩個參數(shù)kCFTypeDictionaryKeyCallBacks和kCFTypeDictionaryValueCallBacks。默認(rèn)的實現(xiàn)就是在添加時進(jìn)行CFRetain,在移除時進(jìn)行CFRelease。
總結(jié)
關(guān)于Extension已經(jīng)學(xué)習(xí)過很久,但是這次的嘗試讓我重新回顧腦海中對Extension的了解;相比之下,Category是大家注意的重點(diǎn),甚至連源碼層面如何實現(xiàn)的Category都有相關(guān)文章。
保持刨坑問底的習(xí)慣,遇到問題時盡量去探究更深一層的原因,這樣自己的知識層便會慢慢擴(kuò)展?;蛟S一開始了解的都是很簡單的問題,但是隨著簡單的問題一一解決,對于更難的問題就可以綜合已經(jīng)學(xué)會的基礎(chǔ)知識作為支撐嘗試解決,久而久之就能觸類旁通。