iOS開發(fā)筆記(十二)— Extension、iOS9Crash、Pod庫和CFDictionary相關(guān)

前言

分享iOS開發(fā)中遇到的問題,和相關(guān)的一些思考,本次內(nèi)容包括:Extension、iOS9Crash、Pod庫和CFDictionary相關(guān)。

正文

一、OC的Extensions特性

先看下圖,這是一段Category中的代碼:(SSPageControllManager+Report.h文件中)

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

Extension

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

Build Active Architecture Only屬性

嘗試重新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);
kCFTypeDictionaryKeyCallBackskCFTypeDictionaryValueCallBacks是什么?
于是展開來看,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ù)kCFTypeDictionaryKeyCallBackskCFTypeDictionaryValueCallBacks。默認(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ǔ)知識作為支撐嘗試解決,久而久之就能觸類旁通。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容