阅读 118

知识点

RAC

热信号和冷信号的区别是什么?

热信号是主动的,即使你没有订阅事件,它仍然会时刻推送。而冷信号是被动的,只有当你订阅的时候,它才会发送消息。

热信号可以有多个订阅者,是一对多,信号可以与订阅者共享信息。而冷信号只能一对一,当有不同的订阅者,消息会从新完整发送。

runtime

@property做了什么?

@property = ivar + getter + setter;

@property默认的属性修饰符

对象: strong atomic readwrite 基本数据类型:assign atomic readwrite

nonatomic 和 atomic 的区别

  • atomic系统生成的getter/setter方法会进行加锁操作,注意:这个锁仅仅保证了getter和setter存取方法的线程安全。而nonatomic就没有这个保证了。所以,nonatomic的速度要比atomic快。
  • 不过atomic可并不能保证线程安全。如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,3种都有可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同时,最终这个属性的值,可能是 B set 的值,也有可能是 C set 的值。

假设有一个 atomic 的属性 "name",如果线程 A 调[self setName:@"A"],线程 B 调[self setName:@"B"],线程 C 调[self name],那么所有这些不同线程上的操作都将依次顺序执行——也就是说,如果一个线程正在执行 getter/setter,其他线程就得等待。因此,属性 name 是读/写安全的。

但是,如果有另一个线程 D 同时在调[name release],那可能就会crash,因为 release 不受 getter/setter 操作的限制。也就是说,这个属性只能说是读/写安全的,但并不是线程安全的,因为别的线程还能进行读写之外的其他操作。线程安全需要开发者自己来保证。

如果 name 属性是 nonatomic 的,那么上面例子里的所有线程 A、B、C、D 都可以同时执行,可能导致无法预料的结果。如果是 atomic 的,那么 A、B、C 会串行,而 D 还是并行的。

atomic在一些情况下是安全的,一些情况下是不安全的,能举一下例子吗

用assign修饰字符串会有什么问题吗?

如果修饰对象,会产生野指针问题;如果修饰基本数据类型则是安全的。 当对象被释放的时候,对象的指针不会置空,该对象会变成野指针,再次对该对象发送消息,会直接崩溃。

unsafeunretain的作用是什么?

unsafe_unretain 指针指向的对象被释放时,unsafe_unretain 指针不会被置为 nil ,而变成了野指针,再次使用就会造成 crash

讲一下你对weak的理解

runtime 中,有四个数据结构非常重要,分别是 SideTables(散列表),SideTable,weak_table_tweak_entry_t。它们和对象的引用计数,以及 weak引用 相关。

runtime 内存空间中,SideTables(散列表) 是一个 8 个元素长度的hash数组,里面存储了 SideTable。而在一个 SideTable 中,又有两个成员,分别是

RefcountMap refcnts;        // 对象引用计数相关 map
weak_table_t weak_table;    // 对象弱引用相关 table
复制代码

其中,refcents 是一个 hash map,其keyobj的地址,而value,则是obj对象的引用计数。 而 weak_table 则存储了 弱引用obj 的指针的地址,其本质是一个以 obj 地址为 key弱引用obj的指针的地址 作为 valuehash表hash表 的节点类型是 weak_entry_t

delloc 先判断是否是 TaggedPointer,如果是的话直接返回。因为 TaggedPointer 它的内存并不存储在堆中,也不需要 mallocfree

  1. object_cxxDestruct(c++析构方法)
  2. _object_remove_assocations 移除关联对象 
  3. 清空引用计数表并清除弱引用表,将weak指针置为nil ,最后free释放

在解决循环引用是时候,为什么用weak修饰?

__weak 修饰的变量,不会出现引用计数+1,也就不会造成 block 强持有外部变量,这样也就不会出现循环引用的问题了。

但是,我们的 block 内部执行的代码中,有可能是一个异步操作,或者延迟操作,此时引用的外部变量可能会变成 nil,导致意想不到的问题,而我们在block内部通过 __strong 修饰这个变量时,block会在执行过程中强持有这个变量,此时这个变量也就不会出现nil的情况,当 block 执行完成后,这个变量也就会随之释放了。

在weak底层,它释放的原理是什么?

把弱引用表里面的(weak对象的地址为键(key)和weak对象的地址的地址为键值(value))的散列表进行清空

动画

UIView和UILayer的职责是什么?

  1. 首先UIView继承自UIResponder可以响应事件,而UILayer继承自NSObject,不可以响应事件.

一个View从A位置平动到B位置,使用UIView的block动画,在这个动画过程中,它的frame和bounds如何变化?在哪个过程中这个UIView的frame发生改变的,这个frame不是放到了一个UIView的动画里面吗,它还有个completion的回调,UIView的frame是哪个阶段开始变化的?

  • frame和bounds是UIView中的两个属性(property)。
  • frame指的是:该view在父view坐标系统中的位置和大小。(参照点是父亲的坐标系统)
  • bounds指的是:该view在本身坐标系统中 的位置和大小。(参照点是本身坐标系统)

-(CGRect)frame{   return CGRectMake(self.frame.origin.x,self.frame.origin.y,self.frame.size.width,self.frame.size.height); }

-(CGRect)bounds{   return CGRectMake(0,0,self.frame.size.width,self.frame.size.height); }

什么是隐式动画,隐式动画的原理?

当图层树被没有获得事务的线程修改的时候将会自动创建隐式事务,当线程的运行循环(run-loop)执行下次迭代的时候将会自动提交事务。比如:

layer.opacity=0.0;
layer.zPosition=-200;
layer.position=CGPointMake(0.0,0.0);
复制代码

图层的 opacity,zPosition 和 position 属性修改,依赖隐式事务来确保动画同时一起发生。

transform在放大两倍的时候,它的frame和bounds是如何变化的?

  • frame 是一个复合属性,由 center、boundstransform 共同计算而来。
  • transform 改变,frame 会受到影响,但是 centerbounds 不会受到影响。也就是你使用 transform 来缩放,bounds 是不会变的。那么由 centerbounds 计算得到的 frame 是永远保持 transformidentity 时的状态。这也是为什么把 transform 设为 identity 后,view 会回归到最初状态。

我有一个可变容器(数组),我对外暴露了增删改查的逻辑,如何实现这个可变容器是线程安全的?

同步串行队列

NSOperationQueue和GCD的区别

  1. GCD是纯C语言的API 。NSOperationQueue是基于GCD的OC的封装。

  2. GCD只支持FIFO队列,NSOperationQueue可以方便设置执行顺序,设置最大的并发数量。

  3. NSOperationQueue可是方便的设置operation之间的依赖关系,GCD则需要很多代码。

  4. NSOperationQueue支持KVO,可以检测operation是否正在执行(isExecuted),是否结束(isFinished),是否取消(isCanceled)

  5. GCD的执行速度比NSOperationQueue快。

我现在有100个任务,现在我想启用5个线程,然后并发的去把100的任务做完,设计一个线程池?

  1. NSOperationQueue设置最大的并发数量
  2. GCD信号量
  3. GCD调度组
  4. GCD栅栏函数

在block解决循环引用的方式

  • weak-strong-dance

  • __block修饰对象(需要注意的是在block内部需要置空对象,而且 block 必须调用)

  • 传递对象 self 作为 block 的参数,提供给 block内部 使用

  • 使用NSProxy

_weak修饰,在block底层有什么不同?

__weak 本身是可以避免循环引用的问题的,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个__strong的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。

__block 本身无法避免循环引用的问题,但是我们可以通过在 block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。另外一点就是__block修饰的变量在 block 内外都是唯一的,要注意这个特性可能带来的隐患。


开发过程中有没有印象深刻的问题?

怎么判断一个单链表有没有形成一个局部不环,如何找到它形成环的入口。如何判断两个单向链表有没有相交?

TCP和UDP的区别

进程和线程的概念、进程间的通讯方式

RunLoop的理解、runloop和线程的关系?哪几种方式能够结束一个runloop?

runloop和线程的关系?

线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。

苹果并没有为我们提供一个可以直接创建Runloop的接口,但是我们可以通过CFRunLoopGetMain()和CFRunLoopGetCurrent()两个方法来获取RunLoop对象

哪几种方式能够结束一个runloop?

第一种启动方式的退出方法

文档说,如果想退出runloop,不应该使用第一种启动方式来启动runloop。

如果runloop没有input sources或者附加的timer,runloop就会退出。
虽然这样可以将runloop退出,但是苹果并不建议我们这么做,因为系统内部有可能会在当前线程的runloop中添加一些输入源,所以通过手动移除input source或者timer这种方式,并不能保证runloop一定会退出。

第二种启动方式runUntilDate:

可以通过设置超时时间来退出runloop。

第三种启动方式runMode:beforeDate:

通过这种方式启动,runloop会运行一次,当超时时间到达或者第一个输入源被处理,runloop就会退出。

如果我们想控制runloop的退出时机,而不是在处理完一个输入源事件之后就退出,那么就要重复调用runMode:beforeDate:,

KVO的实现原理

1和2任务执行完成之后执行3任务?(调度组,栅栏函数、信号量)

现在有A和B两个队列,都是并发队列,1和3任务是在A队列里执行,2任务是在B队列里执行,还是1和2任务执行完成之后执行3任务,调度组,栅栏函数、信号量这三种方式还都能够实现吗?

调度组可以实现

- (**void**)GDDEvent {

    //  群组-统一监控一组任务

    dispatch_group_t group = dispatch_group_create();

    dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_queue_t q2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

    dispatch_queue_t dispatchQueue = dispatch_queue_create("ted.queue.next1", DISPATCH_QUEUE_CONCURRENT);

    // 添加任务

    // group 负责监控任务,queue 负责调度任务

    dispatch_group_async(group, q, ^{

        [NSThread sleepForTimeInterval:1.0];

        NSLog(@"任务1 %@", [NSThread currentThread]);

    });

    dispatch_group_async(group, q2, ^{

        sleep(5);

        NSLog(@"任务2 %@", [NSThread currentThread]);

    });

    dispatch_group_async(group, dispatchQueue, ^{

        NSLog(@"任务3 %@", [NSThread currentThread]);

    });

    // 监听所有任务完成 - 等到 group 中的所有任务执行完毕后,"由队列调度 block 中的任务异步执行"

    dispatch_group_notify(group, dispatchQueue, ^{

        // 修改为主队列,后台批量下载,结束后,主线程统一更新UI

        NSLog(@"OK %@", [NSThread currentThread]);

    });

    NSLog(@"come here");

}

复制代码

调度组输出结果.png

信号量不可以

-(**void**)dispatchSignal{

    //crate的value表示,最多几个资源可访问

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);

    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_queue_t quene2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

    dispatch_queue_t dispatchQueue = dispatch_queue_create("ted.queue.next1", DISPATCH_QUEUE_CONCURRENT);

    //任务1

    dispatch_async(quene, ^{

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        NSLog(@"run task 1");

        sleep(1);

        NSLog(@"complete task 1");

        dispatch_semaphore_signal(semaphore);

    });

    //任务2

    dispatch_async(quene2, ^{

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        NSLog(@"run task 2");

        sleep(1);

        NSLog(@"complete task 2");

        dispatch_semaphore_signal(semaphore);

    });

    //任务3

    dispatch_async(quene2, ^{

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        NSLog(@"run task 3");

        sleep(1);

        NSLog(@"complete task 3");

        dispatch_semaphore_signal(semaphore);

    });

}
复制代码

信号量输出结果.png

栅栏函数也不可以

- (**void**)dispatchBarrierAsyncEvent {

    dispatch_queue_t queue = dispatch_queue_create("testqueue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_queue_t queue2 = dispatch_queue_create("testqueue2", DISPATCH_QUEUE_CONCURRENT);

        dispatch_async(queue2, ^{

            sleep(1);

            NSLog(@"----1-----%@", [NSThread currentThread]);

        });

        dispatch_async(queue, ^{

            NSLog(@"----2-----%@", [NSThread currentThread]);

        });

        dispatch_barrier_async(queue, ^{

            NSLog(@"----barrier-----%@", [NSThread currentThread]);

        });

        dispatch_async(queue, ^{

            NSLog(@"----3-----%@", [NSThread currentThread]);

        });

        dispatch_async(queue, ^{

            NSLog(@"----4-----%@", [NSThread currentThread]);

        });

}
复制代码

栅栏函数输出结果.png

FMDB如何保证多线程安全

在使用FMDB时,不要创建一个单例数据库,然后在多个线程中使用。如果坚持要这么做,系统最终会崩溃会抛出一个异常。如果一定要在多个线程中使用,可以在每个线程中创建一个FMDatabase对象。另外,还有一个更好的方法就是使用FMDatabaseQueue类。用户可以生成一个FMDatabaseQueue单例,然后在多线程中使用它,FMDatabaseQueue会负责同步和协调多线程。

首先来创建一个FMDatabaseQueue队列。

FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
[queue inDatabase:^(FMDatabase *db) {
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];

    FMResultSet *rs = [db executeQuery:@"select * from foo"];
    while ([rs next]) {
        …
    }
}];
复制代码

简单包装成事务的用法如下:

[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];

    if (whoopsSomethingWrongHappened) {
        *rollback = YES;
        return;
    }
    // etc…
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @4];
}];
复制代码

给对象发一个消息,底层是如何执行的

Category给类扩展一个方法,它是如何做到的?

dispatch_once实现单例的底层原理,onceToken什么作用?

dispatch_once 主要是根据 onceToken 的值来决定怎么去执行代码。

  • 当 onceToken = 0 时,线程执行 dispatch_once 的 block 中代码;
  • 当 onceToken = -1 时,线程跳过 dispatch_once 的 block 中代码不执行;
  • 当 onceToken 为其他值时,线程被阻塞,等待 onceToken 值改变。
  1. 当线程调用 shareInstance,此时 onceToken = 0,调用 block 中的代码,此时 onceToken 的值变为 140734537148864。
  2. 当其他线程再调用 shareInstance 方法时,onceToken 的值已经是 140734537148864 了,线程阻塞。
  3. 当 block 线程执行完 block 之后,onceToken 变为 -1,其他线程不再阻塞,跳过 block。
  4. 下次再调用 shareInstance 时,block 已经为 -1,直接跳过 block。

我在屏幕上点击了之后,它在屏幕上是怎么传递的?它的响应过程?

# iOS中触摸事件传递和响应原理

UIViewController的生命周期有哪些?ViewControllerA上 push 到 ViewControllerB,这时候会分别走哪个视图的哪些方法?

初始化方法

 VCA viewDidLoad

 VCA viewWillAppear

 VCA viewWillLayoutSubviews

 VCA viewDidLayoutSubviews

 VCA viewDidAppear
复制代码

push方法

 VCB viewDidLoad

 VCA viewWillDisappear

 VCB viewWillAppear

 VCB viewWillLayoutSubviews

 VCB viewDidLayoutSubviews

 VCA viewDidDisappear

 VCB viewDidAppear
复制代码

pop方法

 VCB viewWillDisappear

 VCA viewWillAppear

 VCB viewDidDisappear

 VCA viewDidAppear

 VCB dealloc
复制代码
文章分类
iOS
文章标签