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

640?wx_fmt=gif


作者丨落影loyinglin

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


640?wx_fmt=gif前言


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


640?wx_fmt=gif正文


一、OC的Extensions特性


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


640?wx_fmt=png

SSPageControllManager+Report.h


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


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


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


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


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


640?wx_fmt=png


如上,新建的是一个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


640?wx_fmt=gif二、 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。


640?wx_fmt=png


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


 
 

    self.timeLabel.frame = CGRectNull;


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


640?wx_fmt=gif问题修复:


问题的触发是因为在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;
}


640?wx_fmt=gif三、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'


640?wx_fmt=png

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


640?wx_fmt=png


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


640?wx_fmt=gif四、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。


640?wx_fmt=gif总结


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


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


【无门槛免费领】

535G超强程序员编程

0基础从入门到精通自学视频教程!

640?wx_fmt=jpeg

640?wx_fmt=jpeg 640?wx_fmt=jpeg

640?wx_fmt=jpeg 640?wx_fmt=jpeg 640?wx_fmt=jpeg

640?wx_fmt=jpeg 640?wx_fmt=jpeg 640?wx_fmt=jpeg

640?wx_fmt=jpeg 640?wx_fmt=jpeg 640?wx_fmt=jpeg

640?wx_fmt=jpeg 640?wx_fmt=jpeg 640?wx_fmt=jpeg 640?wx_fmt=jpeg

640?wx_fmt=png万水千山总是情,点个 “ 在看” 行不行
首先,你需要使用 iOS 中的 Security 框架来进行 RSA 加密操作。以下是一个简单的示例代码,演示如何使用公钥字符串进行 RSA 加密: ```swift func encryptString(_ string: String, publicKey: String) -> String? { guard let data = string.data(using: .utf8) else { return nil } // 将公钥字符串转换为 SecKey 对象 guard let publicKeyData = Data(base64Encoded: publicKey), let publicKeySecKey = try? getPublicKey(from: publicKeyData) else { return nil } // 执行 RSA 加密操作 var error: Unmanaged<CFError>? guard let encryptedData = SecKeyCreateEncryptedData(publicKeySecKey, .rsaEncryptionPKCS1, data as CFData, &error) as Data? else { return nil } return encryptedData.base64EncodedString() } func getPublicKey(from data: Data) throws -> SecKey { // 定义公钥的属性 let attributes: [CFString: Any] = [ kSecAttrKeyType: kSecAttrKeyTypeRSA, kSecAttrKeyClass: kSecAttrKeyClassPublic, kSecAttrKeySizeInBits: 2048 ] // 从数据中创建一个SecKey对象 guard let secKey = SecKeyCreateWithData(data as CFData, attributes as CFDictionary, nil) else { throw EncryptionError.invalidPublicKey } return secKey } enum EncryptionError: Error { case invalidPublicKey } ``` 在上面的代码中,`encryptString` 函数接收一个字符串和一个公钥字符串作为参数,并返回加密后的字符串。首先,将输入字符串转换为数据对象。然后,将公钥字符串转换为 `SecKey` 对象,该对象可以用于执行 RSA 加密操作。最后,使用 `SecKeyCreateEncryptedData` 函数进行加密,返回加密后的数据对象。最后,将加密后的数据对象转换为 Base64 编码的字符串,并返回它。 `getPublicKey` 函数将公钥字符串转换为 `SecKey` 对象。此函数首先定义一个字典,其中包含公钥的属性。然后,使用 `SecKeyCreateWithData` 函数从数据中创建一个 `SecKey` 对象,并将其返回。 需要注意的是,这里使用的是 PKCS#1 v1.5 填充方式进行加密。如果你需要使用其他填充方式,请根据需要更改 `SecKeyCreateEncryptedData` 函数的第二个参数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值