编程开发知识

1.转场动画
iOS7开始苹果新增了一些转场动画给我们,这给我们做动画提供了便利。

UIViewControllerAnimatedTransitioning
UIViewControllerContextTransitioning
UIPercentDrivenInteractiveTransition

这个经常在导航控制器push或pop的时候自己可以添加自定义的转场动画来实现自己想要的结果。
这个也可以实现右滑操作。

- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                        animationControllerForOperation:(UINavigationControllerOperation)operation
                                                     fromViewController:(UIViewController *)fromVC
                                                       toViewController:(UIViewController *)toVC
{
        if (operation == UINavigationControllerOperationPush) {
             return [AnimateInOut new];
        } 
        else if (operation == UINavigationControllerOperationPop) {
             return [AnimateTransition new];
        } 
        return nil;
}

- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                               interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController {
       return nil;
}

2.C语言中的二级指针

int main(void) {
    int tcount = 7;
    int *pcount = &tcount;
    increaseValue(&pcount);

    printf("count = %d\n", tcount);
    printf("pcount = %p\n", pcount);

    int tcu = 8;
    increase(&tcu);

    printf("count = %d\n", tcu);
    printf("tcu = %d\n", tcu);
    return 0;
}

void increaseValue(int **ptr)
{
    **ptr = **ptr + 1;
    *ptr = NULL;
}

void increase(int *ptr)
{
    *ptr = *ptr + 1;
    ptr = NULL;
}

结果:
ount = 8
pcount = 0x0或NULL
count = 9
tcu = 9

一级指针变量,也是一个普通变量,只不过这变量的值是一个内存单元的地址而已。tcu在传递给increase之前,被copy到一个临时变量中,这个临时变量的值是一个地址,可以改变这个地址所在内存单元的值,但是无法改变外部的tcu。

从这个结果可以得出一个结论:一级指针作为参数传递,可以改变外部变量的值,即一级指针所指向的内容,但是却无法改变指针本身(如tcu)。

对于指针操作,有两个概念:

引用:对应于C语言中的&取地址操作
Reference

解引用:在C语言中,对应于->操作。
Dereference operator

对于一个普通变量,引用操作,得到的是一级指针。一级指针传递到函数内部,虽然这个一级指针的值会copy一份到临时变量,但是这个临时变量的内容是一个指针,通过->解引用一个地址可以修改该地址所指向的内存单元的值。

对于一个一级指针,引用操作,得到一个二级指针。相反,对于一个二级指针解引用得到一级指针,对于一个一级指针解引用得到原始变量。一级指针和二级指针的值都是指向一个内存单元,一级指针指向的内存单元存放的是源变量的值,二级指针指向的内存单元存放的是一级指针的地址。

二级指针一般用在需要修改函数外部指针的情况。因为函数外部的指针变量,只有通过二级指针解引用得到外部指针变量在内存单元的地址,修改这个地址所指向的内容即可。

总结
首先,指针变量,它也是一个变量,在内存单元中也要占用内存空间。一级指针变量指向的内容是普通变量的值,二级指针变量指向的内容是一级指针变量的地址。

3.KVO的底层实现。
这是怎么实现的呢?其实这都是通过Objective-C强大的运行时(runtime)实现的。当你第一次观察某个object时,runtime会创建一个新的继承原先class的subclass,命名为NSKVONotifying_CustomClass(CustomClass为自己定义的类)。在这个新的class中,它重写了所有被观察的key,然后将object的isa指针指向新创建的class(这个指针告诉Objective-C运行时某个object到底是哪种类型的object)。所以object神奇地变成了新的子类的实例。

这些被重写的方法实现了如何通知观察者们。当改变一个key时,会触发setKey方法,但这个方法被重写了,并且在内部添加了发送通知机制。(当然也可以不走setXXX方法,比如直接修改iVar,但不推荐这么做)。

有意思的是:苹果不希望这个机制暴露在外部。除了setters,这个动态生成的子类同时也重写了-class方法,依旧返回原先的class!如果不仔细看的话,被KVO过的object看起来和原先的object没什么两样。
来自博客http://limboy.me/ios/2013/08/05/internal-implementation-of-kvo.html

KVO和NSNotificationCenter简述

#import <Foundation/Foundation.h>

@interface Food : NSObject

@property (nonatomic, copy) NSString *name;

@end

控制器主要代码

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(listen:) name:@"NOTIFICATION" object:nil];

_f = [Food new];
_f.name = @"马铃薯";

[_f addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
// Printing description of _f->isa:
// NSKVONotifying_Food
_f.name = @"番薯";


- (void)listen:(NSNotification *)note {
      NSLog(@"%@", [NSThread currentThread]);
      NSLog(@"%@", note.userInfo[@"new"]);
}

- (void)dealloc {
      [_f removeObserver:self forKeyPath:@"name" context:nil];
      [[NSNotificationCenter defaultCenter] removeObserver:self name:@"NOTIFICATION" object:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
     if ([keyPath isEqualToString:@"name"]) {
           NSLog(@"%@", change[NSKeyValueChangeNewKey]);
//        NSLog(@"%@", [NSThread currentThread]);
           dispatch_queue_t queue = dispatch_queue_create("com.quart2DDemo.serialQueue", 0);
           dispatch_async(queue, ^{
           NSLog(@"%@", [NSThread currentThread]);
           [[NSNotificationCenter defaultCenter] postNotificationName:@"NOTIFICATION" object:nil userInfo:change];
       });
     }
     else {
            [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
     }
}

这里主要是想说明KVO的底层原理,简述KVO在监听对象的时候如果监听对象的属性发生改变,KVO会动态的生成一个新的子类,用isa指向它,然后在新的子类里重写属性的set方法,在方法里调用父类的set方法,并通过调用observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context来通知属性的改变。
还有一点就是通知中心的问题,这里说明了在哪个线程发通知,就在哪个线程回调通知的信息。就是说如果在主线程(子线程)发通知,那么回调就在主线程(子线程)。

4.NSRunLoop的原理。

以下内容来自RyanJIN http://www.jianshu.com/p/ebb3e42049fd的简书。
关于NSRunLoop推荐看一下来自百度工程师的分享视频:http://v.youku.com/v_show/id_XODgxODkzODI0.html
RunLoop就是跑圈, 保证程序一直在执行. App运行起来之后, 即使你什么都不做, 放在那儿它也不会退出, 而是一直在"跑圈", 这就是RunLoop干的事. 主线程会自动创建一个RunLoop来保证程序一直运行. 但子线程默认不创建NSRunLoop, 所以子线程的任务一旦返回, 线程就over了.

上面的并发operation当start函数返回后子线程就退出了, 当NSURLConnection的delegate回调时, 线程已经木有了, 所以你也就收不到回调了. 为了保证子线程持续live(等待connection回调), 你需要在子线程中加入RunLoop, 来保证它不会被kill掉.

RunLoop在某一时刻只能在一种模式下运行, 更换模式时需要暂停当前的Loop, 然后重启新的Loop. RunLoop主要有下面几个模式:

NSDefalutRunLoopMode : 默认Mode, 通常主线程在这个模式下运行
UITrackingRunLoopMode : 滑动ScrollView是会切换到这个模式
NSRunLoopCommonModes: 包括上面两个模式
这边需要特别注意的是, 在滑动ScrollView的情况下, 系统会自动把RunLoop模式切换成UITrackingRunLoopMode来保证ScrollView的流畅性.

[NSTimer scheduledTimerWithTimeInterval:1.f
                             target:self
                           selector:@selector(timerAction:)   
                           userInfo:nil
                            reports:YES];

当你在滑动ScrollView的时候上面的timer会失效, 原因是Timer是默认加在NSDefalutRunLoopMode上的, 而滑动ScrollView后系统把RunLoop切换为UITrackingRunLoopMode, 所以timer就不会执行了. 解决方法是把该Timer加到NSRunLoopCommonModes下, 这样即使滑动ScrollView也不会影响timer了.

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
另外还有一个trick是当tableview的cell从网络异步加载图片, 加载完成后在主线程刷新显示图片, 这时滑动tableview会造成卡顿. 通常的思路是tableview滑动的时候延迟加载图片, 等停止滑动时再显示图片. 这里我们可以通过RunLoop来实现.

[self.cellImageView performSelector:@sector(setImage:)
                     withObject:downloadedImage
                     afterDelay:0
                        inModes:@[NSDefaultRunLoopMode]];

当NSRunLoop为NSDefaultRunLoopMode的时候tableview肯定停止滑动了, why? 因为如果还在滑动中, RunLoop的mode应该是UITrackingRunLoopMode.

呼叫NSURLConnection的异步回调
现在解决方案已经很清晰了, 就是利用RunLoop来监督线程, 让它一直等待delegate的回调. 上面已经说到Main Thread是默认创建了一个RunLoop的, 所以我们的Option 1是让start函数在主线程运行(即使[operation start]是在子线程调用的).

- (void)start 
{
     if (![NSThread isMainThread]) {
          [self performSelectorOnMainThread:@selector(start)
                           withObject:nil
                        waitUntilDone:NO];
          return;
    }
// set up NSURLConnection...
}

或者这样:

- (void)start
{
       [[NSOperationQueue mainQueue] addOperationWithBlock:^{
       self.connection = [NSURLConnection connectionWithRequest:self.request delegate:self];
       }];
}

这样我们可以简单直接的使用main run loop, 因为数据delivery是非常快滴. 然后我们就可以将处理incoming data的操作放到子线程去...

Option 2是让operation的start函数在子线程运行, 但是我们为它创建一个RunLoop. 然后把URL connection schedule到上面去. 我们先来瞅瞅AFNetworking是怎么做滴:

+ (void)networkRequestThreadEntryPoint:(id)__unused object 
{
     @autoreleasepool {
          [[NSThread currentThread] setName:@"AFNetworking"];
          NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
          [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
          [runLoop run];
     }
}

+ (NSThread *)networkRequestThread 
{
     static NSThread *_networkRequestThread = nil;
     static dispatch_once_t oncePredicate;
     dispatch_once(&oncePredicate, ^{
     _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
     [_networkRequestThread start];
     });
     return _networkRequestThread;
 }

 - (void)start 
 {
      [self.lock lock];
      if ([self isCancelled]) {
           [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
      } else if ([self isReady]) {
           self.state = AFOperationExecutingState;
           [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
      }
      [self.lock unlock];
 }

AFNetworking创建了一个新的子线程(在子线程中调用NSRunLoop *runloop = [NSRunLoop currentRunLoop]; 获取RunLoop对象的时候, 就会创建RunLoop), 然后把它加到RunLoop里面来保证它一直运行.

这边我们可以简单的判断下当前start()的线程是子线程还是主线程, 如果是子线程则调用[NSRunLoop currentRunLoop]创新RunLoop, 否则就直接调用[NSRunLoop mainRunLoop], 当然在主线程下就没必要调用[runLoop run]了, 因为它本来就是一直run的.
P.S. 我们还可以使用CFRunLoop来启动和停止RunLoop, 像下面这样:

[self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop]
                       forMode:NSRunLoopCommonModes];

CFRunLoopRun();
等到该Operation结束的时候, 一定要记得调用CFRunLoopStop()停止当前线程的RunLoop, 让当前线程在operation finished之后可以退出.
多线程。

上下文切换
容易混淆的几个概念,之前学习计算机操作系统课程的时候老是搞混了。
1). 串行(Serial) VS. 并行(Concurrent)
串行和并行描述的是任务和任务之间的执行方式. 串行是任务A执行完了任务B才能执行, 它们俩只能顺序执行. 并行则是任务A和任务B可以同时执行.

2). 同步(Synchronous) VS. 异步(Asynchronous)
同步和异步描述的其实就是函数什么时候返回. 比如用来下载图片的函数A: {download image}, 同步函数只有在image下载结束之后才返回, 下载的这段时间函数A只能搬个小板凳在那儿坐等... 而异步函数, 立即返回. 图片会去下载, 但函数A不会去等它完成. So, 异步函数不会堵塞当前线程去执行下一个函数!

3). 并发(Concurrency) VS. 并行(Parallelism)
这个更容易混淆了, 先用Ray大神的示意图和说明来解释一下: 并发是程序的属性(property of the program), 而并行是计算机的属性(property of the machine).

图片省略。。。
Concurrency_vs_Parallelism.png
还是很抽象? 那我再来解释一下, 并行和并发都是用来让不同的任务可以"同时执行", 只是并发是伪同时, 而并行是真同时. 假设你有任务T1和任务T2(这里的任务可以是进程也可以是线程):

a. 首先如果你的CPU是单核的, 为了实现"同时"执行T1和T2, 那只能分时执行, CPU执行一会儿T1后马上再去执行T2, 切换的速度非常快(这里的切换也是需要消耗资源的, context switch), 以至于你以为T1和T2是同时执行了(但其实同一时刻只有一个任务占有着CPU).

b. 如果你是多核CPU, 那么恭喜你, 你可以真正同时执行T1和T2了, 在同一时刻CPU的核心core1执行着T1, 然后core2执行着T2, great!

其实我们平常说的并发编程包括狭义上的"并行"和"并发", 你不能保证你的代码会被并行执行, 但你可以以并发的方式设计你的代码. 系统会判断在某一个时刻是否有可用的core(多核CPU核心), 如果有就并行(parallelism)执行, 否则就用context switch来分时并发(concurrency)执行. 最后再以Ray大神的话结尾: Parallelism requires Concurrency, but Concurrency does not guarantee Parallelism!

NSOperation实验课
下面我们进入实验课啦, 要想真正了解某个东东, 还是需要打开Xcode, 写上几行代码, 然后Commard+R. 为了帮Apple提升Xcode的使用率:-D, 我会给出几个case, 童鞋们可以自己编写test code来验证:

1). 创建两个operation, 然后直接[operation start], 在NSOperation并发设计和非并发设计的情况下, 查看这两个operation是否同时执行了(最简单的打log看是不是交替打印).

2). 在主线程和子线程下分别调用[operation start], 看看执行情况.
主线程2016-01-19 18:19:05.916 Demo[5482:2300666] <NSThread: 0x7ff80ac04b20>{number = 1, name = main}, 耗时下载
子线程 2016-01-19 18:19:20.005 Demo[5482:2301611] <NSThread: 0x7ff80ad10e20>{number = 5, name = (null)}, 耗时下载2
3). 创建operation并放到NSOperationQueue里面执行, 分别看看mainQueue和非mainQueue下的执行情况.

4). maxConcurrentOperationCount设置后的执行情况.
最多并发的个数。
5). 试试NSOperation的依赖关系设置, [operationB addDependency:operationA].
operationB依赖于operationA,等待operationA的操作完成后才执行operationB的操作。

6). 写个完整的demo吧, 比如简单的HTTP Downloader.

5.iOS 7中UITableViewCell的变化

iOS 7的UITableViewCell内部与iOS 6有些不同,cell与contentView之间多了一层UITableViewCellScrollView。

下面简单作个比较:

iOS 6中UITableViewCell的View层级

(lldb) po [cell recursiveDescription]
<UITableViewCell: 0xba72e50; frame = (0 0; 320 44); layer = <CALayer: 0xba72fd0>>
| <UITableViewCellContentView: 0xba6f980; frame = (0 0; 320 44); gestureRecognizers = <NSArray: 0xba70960>; layer = <CALayer: 0xba6fb70>>

iOS 7中UITableViewCell的View层级

(lldb) po [cell recursiveDescription]
<UITableViewCell: 0xd984fc0; frame = (0 0; 320 44); layer = <CALayer: 0xd982750>>
| <UITableViewCellScrollView: 0xcca9790; frame = (0 0; 320 44); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0xcc54ec0>; layer = <CALayer: 0xccac430>; contentOffset: {0, 0}>
|    | <UITableViewCellContentView: 0xdb582a0; frame = (0 0; 320 44); gestureRecognizers = <NSArray: 0xdb49c40>; layer = <CALayer: 0xdb54050>>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容

  • Object C中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么? 1...
    AlanGe阅读 1,712评论 0 17
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,118评论 29 470
  • 对于iOS的并发编程, 用的最普遍的就是GCD了, GCD结合Block可以so easy的实现多线程并发编程. ...
    金小俊阅读 7,945评论 32 136
  • 本文将从以下几个部分来介绍多线程。 第一部分介绍多线程的基本原理。 第二部分介绍Run loop。 第三部分介绍多...
    曲年阅读 1,252评论 2 14
  • 辛苦最怜天上月,一昔如环,昔昔都成玦。 01 “每天都有一百次想要杀死老板的冲动!”喊出这句话时,我不禁被自己吓了...
    玉兮颜阅读 553评论 12 12