iOS学习笔记-关于Core Foundation

千言万语都是坑!

问题的触发点

前段时间看到了一个用swift写的下拉刷新的动画PullToBounce
https://github.com/entotsu/PullToBounce
觉得很酷炫,就想用oc来实现一遍,期间踩了不少坑,也算是发现了swift相对于Objective-C的一些方便之处,其中有一段swift代码是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
self.waveLayer.path = self.wavePath(amountX: 0, amountY: 0)
var bounce = CAKeyframeAnimation(keyPath: "path")
bounce.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
var values = [
self.wavePath(amountX: positionX, amountY: positionY),
self.wavePath(amountX: -(positionX * 0.7), amountY: -(positionY * 0.7)),
self.wavePath(amountX: positionX * 0.4, amountY: positionY * 0.4),
self.wavePath(amountX: -(positionX * 0.3), amountY: -(positionY * 0.3)),
self.wavePath(amountX: positionX * 0.15, amountY: positionY * 0.15),
self.wavePath(amountX: 0.0, amountY: 0.0)
]
bounce.values = values
bounce.duration = bounceDuration
bounce.removedOnCompletion = true
bounce.fillMode = kCAFillModeForwards
bounce.delegate = self
self.waveLayer.addAnimation(bounce, forKey: "return")

熟悉Core Animation的同学应该很容易看懂这段代码,waveLayer是一个CAShapeLayer,waveLayer.path是一个CGPathRef,wavePath方法返回的是一个CGPathRef,这段代码实现的是一个关键帧弹性动画,动画的KeyPath是”path”,在swift中,数组可以存放任意类型,当然也包括CF指针,然而在Objective-C中,NSArray只能存放NSObject,也就是说没有办法在数组中加入CGPathRef对象,那么这个动画该怎么实现呢?

后来,我在https://github.com/AttackOnDobby/iOS-Core-Animation-Advanced-Techniques/blob/master/8-%E6%98%BE%E5%BC%8F%E5%8A%A8%E7%94%BB/%E6%98%BE%E5%BC%8F%E5%8A%A8%E7%94%BB.md中找到了一段类似的代码,顿时恍然大悟。

1
2
3
4
5
6
7
8
9
10
11
12
13
- (IBAction)changeColor {
//create a keyframe animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"backgroundColor";
animation.duration = 2.0;
animation.values = @[
(__bridge id)[UIColor blueColor].CGColor,
(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor ];
//apply animation to layer
[self.colorLayer addAnimation:animation forKey:nil];
}

刨根问底

Core Foundation

Core Foundation 是由C语言来实现的,而不是Objective-C,它提供了一些很基础的功能,例如CFString,CFNumber等。

以CFString为例,在OC中所对应的就是NSString
我们可以通过NSStringFromClass()方法来打印一下NSString的实质

1
NSLog(NSStringFromClass([@"This is An NSString" class]);

结果是__NSCFConstantString

NSString其实是一个类簇,所谓类簇,即:
将一些私有的、具体的子类组合在一个公共的、抽象的超类下面,以这种方法来组织类可以简化一个面向对象框架的公开架构,而又不减少功能的丰富性。
简单来说就是抽象的接口,String Objects不是NSString的实力,而是实现了NSString方法的私有类的实力,实质上就是CFString

相互转换

Objective-C和Core Foundation对象之间可以很轻松的转换,拿NSString和CFString为例

1
2
CFStringRef aCFString = (CFStringRef)aNSString;
NSString *aNSString = (NSString *)aCFString;

然而需要注意的一个问题是,对于我这个从来没有在非ARC环境下写过OC的程序员来说,往往不会太注重内存管理这件事,而一旦用到了CF,ARC就无能为力了,很简单,因为它是用C写的。编译器无法自动管理对CF的内存,所以我们一旦使用了CF对象,就必须手段管理。

我们可以使用CFRetain和CFRelease来进行CF对象的内存管理,然而,在Objective-C和Core Foundation相互转换的时候,内存管理又称了一个问题,到底是交给ARC来处理,还是手动处理,一不小心会不会释放两次?又或者造成内存泄露?

三种处理方案

  • __bridge 不改变对象所有权

    所谓不改变对象所有权,就是原来是CF就用CF的管理方式,原来是OC就用OC的管理方式
    比如对于一个NSString,通过(__bridge CFStringRef)将其转换成CFStringRef,它还是会通过ARC来管理内存,反之亦然。

  • __bridge_retained 或者 CFBridgingRetain() 所有权归CF

    这种情况下,ARC的管理权被剥夺,需要手动管理内存
    另外解释一下,CFBridgingRetain()是bridge_retain的宏方法
    即:(
    bridge_retaind CFStringRef) aNSString 相当于 (CFStringRef)CFBridgingRetain(aNSString)

  • __bridge_transfer 或者 CFBridgingRelease() 所有权归ARC

    和上面相反,无需多做解释。