lxian's Blog


  • 首页

  • 归档

  • 标签
lxian's Blog

简单总结pep333

发表于 2016-12-31 |

应用端

提供一个callable 的object. 它应当接受两个参数environ, start_response。并将response body 以一个iterable object 的形式return。

  1. environ
    包含当前request的各种参数
  2. start_reponse
    一个callable object, 接受status,response_headers和 exc_info.
    exc_info 是应用将错误传递给网关端用的。
    start_response 作用是写reponse header,所以需在retrun之前调用
  3. return - iterable response body
    服务器端会读完它并将其写入response

服务器端

每次request 调用应用端端callable 并传入相应的参数。在应用端调用start_response之后,写header,并在应用端返回后,发送header和body

中间件

对应用端看来它是服务器端,服务器端看来它是应用端。也就是说它需要同时实现应用端和服务器端。
所以它需要

  1. 能接收一个应用端程序,并实现自己的start_response,将服务器端的start_response封装进去
  2. callable 并能接收接收environ, start_response,
  3. 在服务器端调用自己的时候,调用应用程序,并且传入自己的start_response

由上面可以看出,中间件可以在两个地方实现自己的功能

  1. 在被服务器端调用的时候,可以改写environ
  2. 在自己封装的start_response 中,可以改写从应用端得到的status, response_headers
  3. 在应用端返回之后,可以改写应用端返回的reponse body
lxian's Blog

GCD Overview

发表于 2016-07-18 |

总结下 GCD.

Basics

CGD 是对线程的抽象。将线程管理交给系统实现,程序员只需要将想执行的任务加入到相应的队列中。

只有global queue 会被调度运行。当创建自己的queue 的时候,实际上会被set target 到global queue 去,custome queue 将要执行的 block 交给 global queue 执行。(存疑,只在一本书上看到这样的描述)

Concurrent Disptach Queue 会由系统来决定应当生成的线程数。而 Serial Dispatch Queue 每创建一条都会创建一条新线程。(大量生成 Serial Queue 会消耗大量内存)

iOS 6.0 之后 GCD 已经加入 ARC, 无需手动释放。

Set target

1
dispatch_set_target(queueA, queueB);

queueA 将 block 传给 queueB 执行

Suspend & Resume

1
2
dispatch_suspend(queue);
dispatch_resume(queue);

queue 被 suspend 之后将停止执行队列中的block. (当前block会继续执行到结束)

dispatch_barrier_async

1
2
3
4
5
6
7
8
9
10
11
dispatch_async(queue, ^{
// read blocks one
});
dispatch_barrier_async(queue, ^{
// write block
});
dispatch_async(queue, ^{
// read blocks two
});

dispatch_barrier_async 加入block 之前的 blocks 执行完毕后, 执行 barrier 加入的 block, 完成之后再执行之后加入的 block

case: read block 用普通的 dispatch_async, write block 用 dispatch_barrier_async。比如一个array,可以用这样的方法来保证其线程安全。

dispatch group

将几个block group 起来,全部执行完后通知用户

1
2
3
4
5
6
7
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, blockA);
dispatch_group_async(group, queue, blockB);
// blockC will be executed after blockA and block B finishes
dispatch_group_notify(group, queue, blockC);

dispatch_group_wait 设置等待时间,到时间直接返回,若为0 则全部完成。设置 DISPATCH_TIME_FOREVER 永远等待,DISPTACH_TIME_NOW 立即返回。

1
2
3
4
5
6
long result = dispatch_group_wait(group, time);
if (result == 0) {
// all blocks finished executing
} else {
// some blocks are not finished yet
}

dispatch_apply

dispatch_sync 的复数版本

1
2
3
4
5
// execute the block on queue 10 times,
// will block current thread until all blocks are finished
dispatch_apply(10, queue, ^(size_t index){
// index of the executing times
});

dispatch semaphore

计数信号

1
2
3
4
5
6
7
8
9
10
// create a semphore with initial count of 10
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
// wait for the chance to reduce 1 to the semaphore
long result = dispatch_semaphore_wait(semaphore, time);
// perform some tasks...
// increacse one to semaphore
dispatch_semaphore_signal(semaphore);

dispatch_once

略

dispatch source

dispatch source data add
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD,
0, 0, queue);
// add handler to the source
__block long totalComplete = 0;
dispatch_source_set_event_handler(source, ^{
long value = dispatch_source_get_data(source);
totalComplete += value;
})
// created cource is in suspended status
// needs to be resumed
dispatch_resume(source);
// sending data to the source
dispatch_async(queue, ^{
for (int i = 0 ; i <= 100; i++) {
dispatch_source_merge_data(source, 1);
usleep(200000);
}
})

注意不可发送0 或者 复数

dispatch timer
1
void dispatch_source_set_timer( dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway);

start - start after the start interval
interval - execution interval. the handler will be called with after the first execution repeately with the interval. set to DISPATCH_TIME_FOREVER if you want to perform it only once. (use dispatch_after if you want to perform some task after a delay only once)
leeway - the time allowed for delay. used a hint to the system

1
2
3
4
5
6
7
8
9
10
11
12
13
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), 1 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
// perform some tasks
});
// to cancel the timer after 10 seconds
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_cancel(timer);
});

Queue sepcific data

Similiar to the Associated Object. Assign a key-value pair to a queue. A release function is required.

1
2
3
4
5
6
7
8
static char key;
CFStringRef *value = CFSTR("some string");
dispatch_queue_set_specific(queue,
&key,
(void*)value,
(dispatch_function_t)CFRelease);
CFStringRef *str = dispatch_get_specific(&key);

如果找不到key, 会向target queue 查询。

例子

1
dispatch_queue_set_specific(q, &key, (__bridge void*)q, NULL);

因为dispatch_get_current_queue 可能会造成死锁,如下,get current queue 检查为当前queue 为queue B, 所以调用 dispatch_sync(queueA, …), 造成死锁(因为最外面还套一个 dispatch_sync(queueA, …))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// a 'not' safe sync
void dispatch_sync_safe(dispatch_queue_t queue, dispatch_block_t block) {
if (dispatch_get_current_queue() == queue) {
block();
} else {
dispatch_sync(queue, block);
}
}
dispatch_sync(queueA, ^{
dispatch_sync(queueB, ^{
dispatch_sync_safe(queueA, ^{
// some tasks...
})
});
});

上面的dispatch_sync_safe 重入,导致deadlock

解决方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static const char sQueueTagKey;
void RNQueueTag(dispatch_queue_t q) {
// Make q point to itself by assignment.
// This doesn't retain, but it can never dangle.
dispatch_queue_set_specific(q, &sQueueTagKey, (__bridge void*)q, NULL);
}
dispatch_queue_t RNQueueCreateTagged(const char *label,
dispatch_queue_attr_t attr) {
dispatch_queue_t q = dispatch_queue_create(label, attr);
RNQueueTag(q);
return q;
}
dispatch_queue_t RNQueueGetCurrentTagged() {
return (__bridge dispatch_queue_t)dispatch_get_specific(&sQueueTagKey);
}
BOOL RNQueueCurrentIsTaggedQueue(dispatch_queue_t q) {
return (RNQueueGetCurrentTagged() == q);
}
BOOL RNQueueCurrentIsMainQueue() {
return [NSThread isMainThread];
}
void RNQueueSafeDispatchSync(dispatch_queue_t q, dispatch_block_t block) {
if (RNQueueCurrentIsTaggedQueue(q)) {
block();
}
else {
dispatch_sync(q, block);
}
}

上面利用Queue sepcific data 会像target queue询问的特性来避免死锁。
不过只能应对这种只有两条queue 的情况,而且只能给最外面的queue 加上标签。(所以有啥用啊。。。)

References

  1. iOS 7 Programming: Pushing the limit
  2. Objective-C 高级编程
lxian's Blog

CALayer hittest 遍历顺序

发表于 2016-01-23 |

CALayer 的 hittest 可以返回一个点所在点最远的layer。那么这个检查sublayer的顺序是怎么样的呢?
为了得到这个顺序,我们先用method swizzling 在 [CALayer hittest:] 里面加上一行打印当前layer 的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@implementation CALayer(hitTestTracking)
static dispatch_once_t hitTestTrackingOnceToken;
+ (void)load
{
dispatch_once(&hitTestTrackingOnceToken, ^{
Class class = [self class];
SEL oriSelector = @selector(hitTest:);
SEL swSelector = @selector(xl_hitTest:);
Method oriMethod = class_getInstanceMethod(class, oriSelector);
Method swMethod = class_getInstanceMethod(class, swSelector);
BOOL addedMethod = class_addMethod(class, oriSelector, method_getImplementation(swMethod), method_getTypeEncoding(swMethod));
if (addedMethod) {
class_replaceMethod(class, swSelector, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
} else {
method_exchangeImplementations(oriMethod, swMethod);
}
});
}
- (CALayer *)xl_hitTest:(CGPoint)p {
CALayer *hittedLayer = [self xl_hitTest:p];
NSLog(@"%@ %@ %@ hitted: %@", NSStringFromSelector(_cmd), self.name, self, (hittedLayer == nil ? @"NO" : @"YES"));
return hittedLayer;
}

为了方便再加上打印sublayers的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)printSubLayers {
[self printSubLayersWithNumberOfIndentation:0];
}
- (void)printSubLayersWithNumberOfIndentation:(NSInteger)numberOfIndentation {
NSInteger n = numberOfIndentation;
while (n--) {
printf("----");
}
printf("%s %s\n", self.name.UTF8String, self.description.UTF8String);
for (CALayer *layer in self.sublayers) {
[layer printSubLayersWithNumberOfIndentation:numberOfIndentation + 1];
}
}

然后在view controller 随便创建一个view, 加入一些 layer 并调用 hittest 来进行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
- (void)viewDidLoad {
[super viewDidLoad];
self.layerView.layer.name = @"layer 0";
CALayer *layer1 = [CALayer layer];
layer1.backgroundColor = [UIColor grayColor].CGColor;
layer1.frame = self.layerView.bounds;
layer1.name = @"layer 1";
CALayer *layer2 = [CALayer layer];
layer2.frame = self.layerView.bounds;
layer2.name = @"layer 2";
CALayer *layer3 = [CALayer layer];
layer3.frame = CGRectMake(100, 100, 5, 5);
layer3.name = @"layer 3";
CALayer *layer11 = [CALayer layer];
layer11.frame = self.layerView.bounds;
layer11.name = @"layer 11";
CALayer *layer111 = [CALayer layer];
layer111.frame = self.layerView.bounds;
layer111.name = @"layer 111";
CALayer *layer12 = [CALayer layer];
layer12.frame = self.layerView.bounds;
layer12.name = @"layer 12";
CALayer *layer21 = [CALayer layer];
layer21.frame = self.layerView.bounds;
layer21.name = @"layer 21";
CALayer *layer31 = [CALayer layer];
layer31.frame = CGRectMake(100, 100, 5, 5);
layer31.name = @"layer 31";
[self.layerView.layer addSublayer:layer1];
[self.layerView.layer addSublayer:layer2];
[self.layerView.layer addSublayer:layer3];
[layer1 addSublayer:layer11];
[layer1 addSublayer:layer12];
[layer11 addSublayer:layer111];
[layer2 addSublayer:layer21];
[layer3 addSublayer:layer31];
CGPoint p = [self.view convertPoint:CGPointZero fromView:self.layerView];
[self.layerView.layer printSubLayers];
CALayer *layerHitted = [self.layerView.layer hitTest:p];
NSLog(@"layer hitted: %@ %@", layerHitted.name, layerHitted);
}

得到如下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
layer 0 <CALayer: 0x7fc62be1e6e0>
----layer 1 <CALayer: 0x7fc62be26ef0>
--------layer 11 <CALayer: 0x7fc62be2c720>
------------layer 111 <CALayer: 0x7fc62be2c740>
--------layer 12 <CALayer: 0x7fc62be10e50>
----layer 2 <CALayer: 0x7fc62be2c6e0>
--------layer 21 <CALayer: 0x7fc62be10e70>
----layer 3 <CALayer: 0x7fc62be2c700>
--------layer 31 <CALayer: 0x7fc62be10e90>
2016-01-24 00:31:50.167 learnLayer[1966:258255] hitTest: layer 111 <CALayer: 0x7fc62be2c740> hitted: YES
2016-01-24 00:31:50.169 learnLayer[1966:258255] hitTest: layer 11 <CALayer: 0x7fc62be2c720> hitted: YES
2016-01-24 00:31:50.170 learnLayer[1966:258255] hitTest: layer 12 <CALayer: 0x7fc62be10e50> hitted: YES
2016-01-24 00:31:50.170 learnLayer[1966:258255] hitTest: layer 1 <CALayer: 0x7fc62be26ef0> hitted: YES
2016-01-24 00:31:50.170 learnLayer[1966:258255] hitTest: layer 21 <CALayer: 0x7fc62be10e70> hitted: YES
2016-01-24 00:31:50.170 learnLayer[1966:258255] hitTest: layer 2 <CALayer: 0x7fc62be2c6e0> hitted: YES
2016-01-24 00:31:50.171 learnLayer[1966:258255] hitTest: layer 31 <CALayer: 0x7fc62be10e90> hitted: NO
2016-01-24 00:31:50.171 learnLayer[1966:258255] hitTest: layer 3 <CALayer: 0x7fc62be2c700> hitted: NO
2016-01-24 00:31:50.172 learnLayer[1966:258255] hitTest: layer 0 <CALayer: 0x7fc62be1e6e0> hitted: YES
2016-01-24 00:31:50.172 learnLayer[1966:258255] layer hitted: layer 21 <CALayer: 0x7fc62be10e70>

从上面我们可以看到,hittest 是用的DFS来遍历图层树,而且不存在短路设置(layer3, layer 31)(其实有短路的,当maskToBounds = YES 的时候就可以有短路了,会在接下来的部分里说明),所有的layer都会被检查一遍,但是最后返回的结果是在遍历中满足条件的最后一个layer (layer 21)。

接着试着将layer31 的位置改一下使得测试点处于layer31 里面:

1
layer31.frame = CGRectMake(-100, -100, 5, 5);

输出变成了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
layer 0 <CALayer: 0x7fb920e9b5b0>
----layer 1 <CALayer: 0x7fb920e90420>
--------layer 11 <CALayer: 0x7fb920e8b730>
------------layer 111 <CALayer: 0x7fb920e8b750>
--------layer 12 <CALayer: 0x7fb920e8df90>
----layer 2 <CALayer: 0x7fb920e06460>
--------layer 21 <CALayer: 0x7fb920e8dfb0>
----layer 3 <CALayer: 0x7fb920e8b680>
--------layer 31 <CALayer: 0x7fb920e90c50>
2016-01-24 00:39:13.951 learnLayer[2076:266220] hitTest: layer 111 <CALayer: 0x7fb920e8b750> hitted: YES
2016-01-24 00:39:13.952 learnLayer[2076:266220] hitTest: layer 11 <CALayer: 0x7fb920e8b730> hitted: YES
2016-01-24 00:39:13.953 learnLayer[2076:266220] hitTest: layer 12 <CALayer: 0x7fb920e8df90> hitted: YES
2016-01-24 00:39:13.953 learnLayer[2076:266220] hitTest: layer 1 <CALayer: 0x7fb920e90420> hitted: YES
2016-01-24 00:39:13.953 learnLayer[2076:266220] hitTest: layer 21 <CALayer: 0x7fb920e8dfb0> hitted: YES
2016-01-24 00:39:13.953 learnLayer[2076:266220] hitTest: layer 2 <CALayer: 0x7fb920e06460> hitted: YES
2016-01-24 00:39:13.953 learnLayer[2076:266220] hitTest: layer 31 <CALayer: 0x7fb920e90c50> hitted: YES
2016-01-24 00:39:13.953 learnLayer[2076:266220] hitTest: layer 3 <CALayer: 0x7fb920e8b680> hitted: YES
2016-01-24 00:39:13.953 learnLayer[2076:266220] hitTest: layer 0 <CALayer: 0x7fb920e9b5b0> hitted: YES
2016-01-24 00:39:13.954 learnLayer[2076:266220] layer hitted: layer 31 <CALayer: 0x7fb920e90c50>

最终的结果变成了layer31,从这里我们就可以知道为什么不存在简单的短路设置(如果当前图层的位置的大小不满足条件就不遍历其子图层)了,因为子图层的位置与父图层并不存在直接关系,一个不在父图层中的点仍可能在其子图层中。

让我们再来把layer3 的maskToBounds 设为 YES:

1
layer3.masksToBounds = YES;

再跑一遍,输出又变了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
layer 0 <CALayer: 0x7fb8f1488f80>
----layer 1 <CALayer: 0x7fb8f1403cd0>
--------layer 11 <CALayer: 0x7fb8f1489a20>
------------layer 111 <CALayer: 0x7fb8f148b880>
--------layer 12 <CALayer: 0x7fb8f148b8a0>
----layer 2 <CALayer: 0x7fb8f14879e0>
--------layer 21 <CALayer: 0x7fb8f148b8c0>
----layer 3 <CALayer: 0x7fb8f1489a00>
--------layer 31 <CALayer: 0x7fb8f1489de0>
2016-01-24 00:47:55.756 learnLayer[2097:272073] hitTest: layer 111 <CALayer: 0x7fb8f148b880> hitted: YES
2016-01-24 00:47:55.757 learnLayer[2097:272073] hitTest: layer 11 <CALayer: 0x7fb8f1489a20> hitted: YES
2016-01-24 00:47:55.757 learnLayer[2097:272073] hitTest: layer 12 <CALayer: 0x7fb8f148b8a0> hitted: YES
2016-01-24 00:47:55.757 learnLayer[2097:272073] hitTest: layer 1 <CALayer: 0x7fb8f1403cd0> hitted: YES
2016-01-24 00:47:55.757 learnLayer[2097:272073] hitTest: layer 21 <CALayer: 0x7fb8f148b8c0> hitted: YES
2016-01-24 00:47:55.758 learnLayer[2097:272073] hitTest: layer 2 <CALayer: 0x7fb8f14879e0> hitted: YES
2016-01-24 00:47:55.758 learnLayer[2097:272073] hitTest: layer 3 <CALayer: 0x7fb8f1489a00> hitted: NO
2016-01-24 00:47:55.772 learnLayer[2097:272073] hitTest: layer 0 <CALayer: 0x7fb8f1488f80> hitted: YES
2016-01-24 00:47:55.772 learnLayer[2097:272073] layer hitted: layer 21 <CALayer: 0x7fb8f148b8c0>

我们可以看到当开启maskToBounds 之后,便开始有短路了,layer31 不再出现在遍历树里面。

lxian's Blog

用Avfoundation 录像(续)

发表于 2015-12-06 |

除了直接用AVCaptureMovieFileOutput,还可以把AVCaptureVideoDataOutput,AVCaptureAudioDataOutput 当作output。这两个class的delegate AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAudioDataOutputSampleBufferDelegate 有一个- (void)captureOutput:(AVCaptureOutput )captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection )connection 的method, 开始录像之后你可以从这个method 里面拿到每一个录下的frame,然后可以用AVAssetWriter来写入local的文件。

  1. 加output

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    _videoDataOutput = [AVCaptureVideoDataOutput new];
    _audioDataOutput = [AVCaptureAudioDataOutput new];
    if ([_captureSession canAddOutput:_videoDataOutput]) {
    DLog(@"added video out");
    [_captureSession addOutput:_videoDataOutput];
    }
    _videoConnection = [_videoDataOutput connectionWithMediaType:AVMediaTypeVideo];
    _videoConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
    [_videoDataOutput setSampleBufferDelegate:self queue:_videoDataOutputQueue];
    if ([_captureSession canAddOutput:_audioDataOutput]) {
    DLog(@"added audio out");
    [_captureSession addOutput:_audioDataOutput];
    }
    _audioConnection = [_audioDataOutput connectionWithMediaType:AVMediaTypeAudio];
    [_audioDataOutput setSampleBufferDelegate:self queue:_audioDataOutputQueue];
  2. 准备AVAssetWriter, 需要两个writerInput 分别写入video 和audio

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    _assetWriter = [AVAssetWriter assetWriterWithURL:[self outputURL] fileType:AVFileTypeMPEG4 error:nil];
    _videoAssetWriterInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo
    outputSettings:[_videoDataOutput recommendedVideoSettingsForAssetWriterWithOutputFileType:AVFileTypeMPEG4]];
    _videoAssetWriterInput.expectsMediaDataInRealTime = YES;
    _audioAssetWriterInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio
    outputSettings:[_audioDataOutput recommendedAudioSettingsForAssetWriterWithOutputFileType:AVFileTypeMPEG4]];
    _audioAssetWriterInput.expectsMediaDataInRealTime = YES;
    if ([_assetWriter canAddInput:_videoAssetWriterInput]) {
    DLog(@"added video out writer");
    [_assetWriter addInput:_videoAssetWriterInput];
    }
    if ([_assetWriter canAddInput:_audioAssetWriterInput]) {
    DLog(@"added audio out writer");
    [_assetWriter addInput:_audioAssetWriterInput];
    }
  3. 填写 - (void)captureOutput:(AVCaptureOutput )captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection )connection

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
    {
    CFRetain(sampleBuffer);
    CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);
    CMMediaType mediaType = CMFormatDescriptionGetMediaType(formatDesc);
    CMTime currentSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
    if (mediaType == kCMMediaType_Video) {
    if(_firstSample) {
    if ([_assetWriter startWriting]) {
    [_assetWriter startSessionAtSourceTime:currentSampleTime];
    _firstSample = NO;
    } else {
    DLog(@"Error when starting avseet writer, %ld %@, %@", (long)_assetWriter.status, [_assetWriter.error localizedDescription], [_assetWriter.error userInfo]);
    }
    return;
    }
    if (_videoAssetWriterInput.readyForMoreMediaData) {
    [_videoAssetWriterInput appendSampleBuffer:bufferToWrite];
    }
    } else if (mediaType == kCMMediaType_Audio && !_firstSample) {
    if (_audioAssetWriterInput.readyForMoreMediaData) {
    [_audioAssetWriterInput appendSampleBuffer:bufferToWrite];
    }
    }
    CFRelease(sampleBuffer);
    }
  4. 在didOutputSampleBuffer里面拿到sample buffer 之后还可以对其进行各种处理--将其转化为CIImage ,然后就可以apply 各种filter。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    CFRetain(pixelBuffer);
    CVPixelBufferLockBaseAddress(pixelBuffer, 0 );
    CIImage *img = [CIImage imageWithCVPixelBuffer:pixelBuffer];
    CIFilter *filter = [CIFilter filterWithName:<filterName>]; //具体 filter可以参考[Core Image Filter Reference](https://developer.apple.com/library/prerelease/mac/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html)
    ... apply filters
    CIImage *outputImage = [filter outputImage];
    [_ciContext render:outputImage toCVPixelBuffer:pixelBuffer]; //把处理好的ciimage 再写回buffer
    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
    CFRelease(pixelBuffer);
  5. 那么live preview 呢,其实如果没有别的要求的话可以直接用之前用的AVCaptureVideoPreviewLayer,但是如果要在live preview 里面显示filter 的效果的话。那么就要将frame 画到一个GLKView 上面,然后把这个view 展示给user.

    create GLKView。这里要用 EAGLContext来create CIContext 这样具体draw的时候就会用GPU 来draw 了,否则将使用CPU 来画

    1
    2
    3
    4
    5
    6
    _eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    _ciContext = [CIContext contextWithEAGLContext:_eaglContext options:@{kCIContextWorkingColorSpace : [NSNull null]} ];
    GLKView *glkView = [[GLKView alloc] initWithFrame:<frame> context:_eaglContext];
    [glkView bindDrawable];
    _livePreviewView = glkView;

    画frame

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    CFRetain(pixelBuffer);
    CVPixelBufferLockBaseAddress(pixelBuffer, 0 );
    CIImage *img = [CIImage imageWithCVPixelBuffer:pixelBuffer];
    [_ciContext drawImage:outputImage inRect:previewRect fromRect:cropRect];
    [((GLKView *)_livePreviewView) display];
    CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
    CFRelease(pixelBuffer);
  6. 最后保存到Library

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [_captureSession stopRunning];
    [_videoAssetWriterInput markAsFinished];
    [_audioAssetWriterInput markAsFinished];
    [_assetWriter finishWritingWithCompletionHandler:^{
    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
    if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:videoURL]) {
    [library writeVideoAtPathToSavedPhotosAlbum:videoURL completionBlock:^(NSURL *assetURL, NSError *error){
    if (error) {
    ALog(@"Error when saving captured video to library, error = %@, url = %@", error, assetURL);
    }
    }];
    }
    }];
lxian's Blog

AutoLayout Content Size

发表于 2015-11-15 |

主要记下Intrinsic Size, Content Hugging/Compression Resistance Priority 是干什么的。

Intrinsic Size

一个view 原本应该有的size,比如一个text label 就是text label 显示完整的text 需要的size, image view 就是这个image 的size。有了Intrinsic Size 之后,再specify 了关于location 的constriant, autolayout 就可以帮你摆放这个view 了。

Content Compression Resitance Priority

一个View 反缩小的Priority。

Content Hugging Priority

一个View 反放大的Priority。

lxian's Blog

Learning Cocoa with Objc reading notes

发表于 2015-11-13 |

Learning Cocoa with Objective-C 这本书我差不多读了一半,感觉这本书比较像一本参考书,要做某个功能可以参考下了解一下基本的东西。跳过了一些章节,把自己之前不知道的一些东西记一下。

NSData

NSData contains bytes with no assumptions of the type of data.

1
2
//load file into NSData
NSData *loadedData = [NSData dataWithContentsOfFile:<filePath>];

NSData itself is useless

1
2
3
NSString *loadedString = [[NSString alloc] initWithData:loadedData
encoding:NSUTF8Encoding];
//encoding:NSUTF8Encoding tells NSString how to interrprate the bytes in NSData

NSCoding

Serialization and Deserialization in Cocoa

1
2
3
4
5
6
7
8
9
10
// confirming to NSCoding
- (void)encodeWithCoder:(NSKeyedArchiver *)aCoder{
[aCoder encodeObject:<obj> forKey:<key>];
}
- (void)initWithCoder:(NSKeyedArchiver *)aCoder{
...
_obj = [aDecoder decodeObjectForKey:<key>]
return self;
}

Working with NSData

1
2
3
4
5
6
// store
NSData *storedData = [NSKeyedArchiver archivedDataWithRootObject:myObject];
[storedData writeToFile:<filePath> atomically:YES];
// retrive
SomeObj *obj = [NSKeyedUnarchiver unarchiveObjectWithData:storedData];

Using NSBundle to find resources

1
NSString *path = [[NSBundle mainBundle] pathForResource:@"SomeImage" ofType:@"png"];

MultiTasking on iOS

1
- (void)applicationDidEnterBacktround:(UIApplication *)application

Any task like saving to disk needs to be performed at this moment. Background app can be killed at anytime and no code can be executed at while being suspended.

Request to run in background

App can request to run in background for no more 10 mins

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// beginBackgroundTaskWithExpirationHandler returns a unqie ident for the task
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}
dispatch_async(dispatch_get_global_queue(DISPTACH_QUEU_PRIORITY_DEFAULT, 0), ^{
....bgTasks...
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
})
}

Tasks that can run for a longer time

  1. playing audio
  2. tracking location
  3. VoIP

Nib

Nib – “freeze-drying” objects and storing them in a serialized form inside the file.
When displaying, loads the nib file, “rehydrates” the stored bojects, and presents them to the user.

How Nib Files are Loaded

  1. re-create every objects in the nib
  2. connect evey outlet
  3. every single object loaded receives awakeFromNib: message

Blocks

1
<return type>(^ <variable name>) (<params>)

Blocks exist in stack when created, thus then assign to like instance variable, the block needs to be copied to the heap.

1
_var = [bomeBlock copy];

Blocks keep strong ref to variables captured.
To modify captured variables, add __block modifier.

1
__block int i = 0;

KVC

  1. accessing key that doesn’t exist causes exception
  2. is able to access even private vriables
    1
    2
    3
    4
    5
    // determine the varibale to set/get at run time
    for (NSString * key in dict) {
    NSObject *value = [dict objectForKey:key];
    [aProduct setValue:value forKey:key];
    }

KVO

  1. synthesized property auto notify changes when setter is called
  2. overried setter -> needs to fire notifiction manually
    1
    2
    3
    4
    5
    - (void)setName:(NSString *)name {
    [self willChangeValueForKey:@"name"];
    _name = name;
    [self didChangeValueForKey:@"name"];
    }

NSNotification

broadcast notifications, typically one singleton notification center.

lxian's Blog

用Avfoundation 录像

发表于 2015-10-27 |

参考了Capturing Video on iOS, CUSTOM CAMERA ON IOS – AVCAPTURESESSION, AVCAPTUREVIDEOPREVIEWLAYER TUTORIAL。(吐槽一下Capturing Video on iOS… sample code 搞那么复杂干嘛。。)

先开一个新的AVCaptureSession

1
2
AVCaptureSession *captureSession = [AVCaptureSession new];
[captureSession setSessionPreset:AVCaptureSessionPresetHigh];// the video quiality

然后加入audio 和video inputs

audio

1
2
3
4
5
6
7
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];//allow recording and replaying at the same time
[audioSession setActive:YES error:nil];
AVCaptureDeviceInput *micDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio] error:&error];
if ([captureSession canAddInput:micDeviceInput]) {
[captureSession addInput:micDeviceInput];
}

video

1
2
3
4
5
6
7
8
9
10
11
12
13
14
AVCaptureDevice *cameraDevice = nil;
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
// make it the front camera
for (AVCaptureDevice *device in devices) {
if ([device position] == AVCaptureDevicePositionFront) {
cameraDevice = device;
}
}
AVCaptureDeviceInput *cameraDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:cameraDevice error:&error];
if ([captureSession canAddInput:cameraDeviceInput]) {
[captureSession addInput:cameraDeviceInput];
}

然后把output也连上

1
2
3
4
AVCaptureMovieFileOutput *movieFileOutput = [AVCaptureMovieFileOutput new];
if([captureSession canAddOutput:movieFileOutput]){
[captureSession addOutput:movieFileOutput];
}

最后再加个显示live preview 的layer

1
2
3
4
5
6
AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:captureSession];
UIView *camView = [[UIView alloc] initWithFrame:CGRectMake(...)];
previewLayer.frame = camView.bounds;
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[camView.layer addSublayer:previewLayer];
[self.view addSubview:camView];

开始录像

1
2
3
NSURL *outputURL = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@tmp.mov", NSTemporaryDirectory()]];
[captureSession startRunning];
[movieFileOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];

结束&保存

1
2
3
[_captureSession stopRunning];
[_movieFileOutput stopRecording];
UISaveVideoAtPathToSavedPhotosAlbum(_movieFileOutput.outputFileURL.path, nil, NULL, NULL);

完🐶

lxian's Blog

Core Data (Part 1)

发表于 2015-10-19 |

现在在读Marcus S. Zarra的Core Data。自己也写了一些code来尝试core data。所以在这里总结一下。不对的地方请指正:)

基础

Core Data 对几个部分:NSManagedObjectModel, NSPersistentStoreCoordinator, NSPersistentStore, NSManagedObjectContext, NSManagedObject

NSManagedObjectModel

NSManagedObjectModel 就是你在xcode里面创建的.xcdatamodel 编译过后得到的.mom 的binary file。 它是我们创建的data model。
加载NSManagedObjectModel就是用这个.mom file 来 init 一个NSManagedObjectModel:

1
2
3
4
5
6
7
8
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"ModelName" withExtension:@"momd"];
ZAssert(modelURL, @"Failed to find model URL");
NSManagedObjectModel *mom = nil;
mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
ZAssert(mom, @"Failed to initialize model");

顺便安利一下ZAssert,详见MY CURRENT PREFIX.PCH FILE by Marcus Zarra

NSPersistentStoreCoordinator

NSPersistentStoreCoordinator 是Core Data的核心,负责loading, caching data 还有持久化。而NSPersistentStore 就是持久化的目的地,比如本地的SQlite Database。因为加载NSPersistentStore的时间可能很长(比如从iCould),所以应当异步执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
NSPersistentStoreCoordinator *psc = nil;
psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
dispatch_queue_t queue = NULL;
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *dictArr = [fm URLsForDirectory:NSDocumentationDirectory inDomains:NSUserDomainMask];
NSURL *storeURL = nil;
NSError *error = nil;
storeURL = [dictArr lastObject];
if (![fm fileExistsAtPath:[storeURL absoluteString]]) {
[fm createDirectoryAtURL:storeURL withIntermediateDirectories:YES attributes:nil error:&error];
if (error) {
ALog(@"Error creating store dictionary %@\n%@", [error localizedDescription], [error userInfo]);
}
}
storeURL = [storeURL URLByAppendingPathComponent:@"TableForDummies.sqlite"];
NSPersistentStore *store = nil;
store = [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];
ZAssert(store, @"Error adding persistent store to coordinator %@\n%@", [error localizedDescription], [error userInfo]);
});

NSManagedObjectContext

上面几个在init 之后基本都不会再碰了,而NSManagedObjectContext将会被用来保存和读取数据。
NSManagedObjectContext 只需要NSPersistentStore 就可以init 了,为了确保程序其他地方可以及时调用到NSManagedContext,NSManagedContext应该在NSPersistentStore init 之后init

1
2
3
4
NSManagedObjectContext *moc = nil;
NSManagedObjectContextConcurrencyType ccType = NSMainQueueConcurrencyType;
moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:ccType];
[moc setPersistentStoreCoordinator:psc];

同时由于NSManagedContext 不是线程安全的,这里用NSMainQueueConcurrencyType,之后对于NSManagedContext 的调用都应该在主线程里。

Fetching & Inserting

Fetch

Fetch 的时候要用NSFetchRequest,会得到一条NSManagedObject 的array

1
2
3
4
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"EntityName" inManagedObjectContext:managedObjectContext]];
NSError *error = nil;
NSArray *results = [moc executeFetchRequest:request error:&error];

为fetchRequest 加上filter 和 sorter (否则取回来的东西顺序是随机的)

1
2
3
4
5
6
7
8
// filter
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"salary > %i", basicSalary];
[request setPredicate:predicate];
// sorter
NSMutableArrary *sortArray = [NSMutableArray array] // 可能有多个sorter
[sortArray addObject:[[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]];
[request setSortDescriptor:sortArray];

NSManagedObject 拿到之后就可以用KVC的方法来set/get 里面的值(包括relationship, one-to-many的会得到一个装了相应mangedObject的NSSet)

1
2
3
4
5
[managedObject setValue:value forKey:@"key"];
[managedObject valueForKey:@"key"];
[managedObject valueForKey:@"someOneToManyRelationship"]; // return NSSet filled with cooresponding NSMangedObject on the other side of the relationship
// 如果需要改变(添加/删除)这个这个Set里面的一些Object
[managedObject mutableSetValueForKey:@"someOneToManyRelationship"]; // return a mutableSet allowing to add/delete object in the relationship

Insert

Insert 则是如下创建一个NSManagedObject 然后set 相应的值就好了

1
2
3
NSManagedObject *obj = [NSEntityDescription
insertNewObjectForEntityForName: @"ObjName"
inManagedObjectContext:context];

Save

最后Save 所有的changes, 就将数据持久化了

1
2
3
4
[context save:&error];
if (error) {
ALog(...)
}

更简单的用法

subclass NSMangedObject

用KVC来设置NSManagedObject 显然需要一大段code。而更简单的方法就是直接subclass NSManagedObject, 例如

1
2
3
4
5
@interface MyMO: NSManagedObject
@property (strong) NSString *name;
@property (strong) NSSet *someOneToManyRelationship; // relationship也一样
....

别忘了要设成dynamic, 因为这些properity 都是在运行时create 的

1
2
3
@dynamic name;
@dynamic someOneToManyRelationship;
...

然后就可以像直接取用了

1
2
3
MyMO obj = [...]
obj.name = "xxx"
...

超方便是不是👍

stored fetch request

而fetch request 也是可以存起来的,如下图添加一个fetch request,可以设置fetch的条件

用的时候..

1
NSFetchRequest *request = [[[moc persistentStoreCoordinator] managedObjectModel] fetchRequestTemplateForName:@"allDummiesNamedZert"];

lxian's Blog

如何extend UIView

发表于 2015-10-14 |

主要是自己搞了好久才搞对,所以在这里记一下。。。
extend 出来的class里面要自己load 一下相应的nib

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@implementation CutsomeUIView
- (id)init
{
self = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class) owner:self options:nil][0];
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class) owner:self options:nil][0];
if (self) {
self.frame = frame;
}
return self;
}
@end

然后xib 里面把class 改成 CustomeUIView。注意是改View的class, 不是File’s Owner… 我之前搞成File’s Owner,然后又把所有的outlet都拉到File’s Owner里面,找了半个小时才找到哪里错了。。。

这样就完成啦~🐶

lxian's Blog

Swift Optional

发表于 2015-10-10 |

本🐶正在学习Swift.被Optional 各种?和!搞得有点晕。参考了Ref1: Swift Optionals: When to use if let, when ? and !, when as? and as, Ref2: What is an optional value in Swift?之后总结一下Optional是怎么回事,要怎么用。
本文的例子都来自于Ref1。

为什么有 Optional

在obj-c 里面我们可以用nil 来表示空值,比如

1
NSArray arr = nil;//这样我们就都知道arr 没有被赋值

但是Swift里面我们并不能将nil直接assign给变量,因为Swift 是type safe的。

Swift also introduces optional types, which handle the absence of a value. Optionals say either “there is a value, and it equals x” or “there isn’t a value at all”. Optionals are similar to using nil with pointers in Objective-C, but they work for any type, not just classes. Optionals are safer and more expressive than nil pointers in Objective-C and are at the heart of many of Swift’s most powerful features.

Optionals are an example of the fact that Swift is a type safe language. Swift helps you to be clear about the types of values your code can work with. If part of your code expects a String, type safety prevents you from passing it an Int by mistake. This enables you to catch and fix errors as early as possible in the development process.

不允许nil, 就避免了在这个变量上操作的unexpected behavior。
但还是得有个方法来表示空值啊,于是就有了Optional,Optional就代表这个变量(比如NSArray)可以装两种东西,一种是nil, 一种是自己的类型(NSArray)。
Optional 背后是类似这样的东西(经过了语法糖包装的)

1
2
3
4
5
6
enum Optional : NilLiteralConvertible {
case None
case Some(T)
}
// Int? 已经不是Int 而是Optional<Int> ...
var height: Optional<Int> = Optional<Int>(180)

如何使用 Optional 变量

简单讲就是,如果你确定一定有值,那么就用!
如果你不确定,而且不在乎没有值的情况,用?
要handle 没有值的情况,用if let

! 很好理解
?一个例子就是,你可能有一个navigationController。

1
controller.navigationController?.pushViewController(myViewController, animated: true)

你想用它来push 一个view 且只当它存在才push。如果navController 存在,这一句就会执行,不然整句都会被当成nil

if let 允许你这样写

1
2
3
4
5
if let nav = controller.navigationController {
nav.pushViewController(myViewController, animated: true)
} else {
//show an alert ot something else
}

12
lxian2

lxian2

11 日志
14 标签
© 2017 lxian2
由 Hexo 强力驱动
主题 - NexT.Pisces