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_t和weak_entry_t。它们和对象的引用计数,以及 weak引用 相关。
在 runtime 内存空间中,SideTables(散列表) 是一个 8 个元素长度的hash数组,里面存储了 SideTable。而在一个 SideTable 中,又有两个成员,分别是
RefcountMap refcnts; // 对象引用计数相关 map
weak_table_t weak_table; // 对象弱引用相关 table
其中,refcents 是一个 hash map,其key是obj的地址,而value,则是obj对象的引用计数。
而 weak_table 则存储了 弱引用obj 的指针的地址,其本质是一个以 obj 地址为 key,弱引用obj的指针的地址 作为 value 的 hash表。hash表 的节点类型是 weak_entry_t。
delloc 先判断是否是 TaggedPointer,如果是的话直接返回。因为 TaggedPointer 它的内存并不存储在堆中,也不需要 malloc 和 free。
object_cxxDestruct(c++析构方法)_object_remove_assocations移除关联对象- 清空引用计数表并清除弱引用表,将
weak指针置为nil,最后free释放
在解决循环引用是时候,为什么用weak修饰?
__weak 修饰的变量,不会出现引用计数+1,也就不会造成 block 强持有外部变量,这样也就不会出现循环引用的问题了。
但是,我们的 block 内部执行的代码中,有可能是一个异步操作,或者延迟操作,此时引用的外部变量可能会变成 nil,导致意想不到的问题,而我们在block内部通过 __strong 修饰这个变量时,block会在执行过程中强持有这个变量,此时这个变量也就不会出现nil的情况,当 block 执行完成后,这个变量也就会随之释放了。
在weak底层,它释放的原理是什么?
把弱引用表里面的(weak对象的地址为键(key)和weak对象的地址的地址为键值(value))的散列表进行清空
动画
UIView和UILayer的区别是什么?
布局
UIView 有三个比较重要的布局属性: frame , bounds 和 center , CALayer 对应地叫做 frame , bounds 和 position 。为了能清楚区分,图层用了“position”,视图用了“center”,但是他们都代表同样的值。
锚点
默认来说, anchorPoint 位于图层的中点,所以图层的将会以这个点为中心放置。 anchorPoint 属性并没有被 UIView 接口暴露出来,这也是视图的position属性被叫做“center”的原因。但是图层的 anchorPoint 可以被移动,比如你可以把它置于图层 frame 的左上角,于是图层的内容将会向右下角的 position 方向移动(如图),而不是居中了。
案例:中标利用视图的
transform 属性来旋转钟表,设置锚点 anchorPoint。
坐标系
和 UIView 严格的二维坐标系不同, CALayer 存在于一个三维空间当中。CALayer 还有另外两个属性, zPosition 和 anchorPointZ ,二者都是在Z轴上描述图层位置的浮点类型。
通常,图层是根据它们子图层的 sublayers 出现的顺序来类绘制的,这就是所谓的画家的算法--就像一个画家在墙上作画--后被绘制上的图层将会遮盖住之前的图层,但是通过增加图层的 zPosition,就可以把图层向相机方向前置,于是它就在所有其他图层的前面了。
事件响应
- 首先
UIView继承自UIResponder可以响应事件,而UILayer继承自NSObject,不可以响应事件. CALayer并不关心任何响应链事件,所以不能直接处理触摸事件或者手势。但是它有一系列的方法帮你处理事件:-containsPoint:和-hitTest:。containsPoint:接受一个在本图层坐标系下的CGPoint,如果这个点在图层frame范围内就返回 YES-hitTest:方法同样接受一个CGPoint类型参数,而不是BOOL类型,它返回图层本身,或者包含这个坐标点的叶子节点图层。注意当调用图层的-hitTest:方法时,测算的顺序严格依赖于图层树当中的图层顺序(和UIView处理事件类似)。之前提到的zPosition属性可以明显改变屏幕上图层的顺序,但不能改变事件传递的顺序。这意味着如果改变了图层的z轴顺序,你会发现将不能够检测到最前方的视图点击事件,这是因为被另一个图层遮盖住了,虽然它的zPosition值较小,但是在图层树中的顺序靠前。
一个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); }
什么是隐式动画,隐式动画的原理?
了解什么是隐式动画前,要先了解是什么根层和非根层?
-
根层:UIView内部自动关联着的那个layer我们称它是根层。
-
非根层:自己手动创建的层,称为非根层。
隐式动画就是当对非根层的部分属性进行修改时, 它会自动的产生一些动画的效果。我们称这个默认产生的动画为隐式动画。
当你改变 CALayer 的一个可做动画的属性,它并不能立刻在屏幕上体现出来。相反,它是从先前的值平滑过渡到新的值。这一切都是默认的行为,你不需要做额外的操作。这其实就是所谓的隐式动画。之所以叫隐式是因为我们并没有指定任何动画的类型。我们仅仅改变了一个属性,然后Core Animation来决定如何并且何时去做动画。比如下面这个例子:
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *layerView;
@property (nonatomic, strong) CALayer *colorLayer;
@end
@implementation ViewController
- (void)viewDidLoad {=
[super viewDidLoad];
//create sublayer
self.colorLayer = [CALayer layer];
self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
self.colorLayer.backgroundColor = [UIColor blueColor].CGColor;
//add it to our view
[self.layerView.layer addSublayer:self.colorLayer];
}
- (IBAction)changeColor {
//randomize the layer background color
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
}
@end
但当你改变一个属性,Core Animation是如何判断动画类型和持续时间的呢?实际上动画执行的时间取决于当前事务的设置,动画类型取决于图层行为。
事务实际上是Core Animation用来包含一系列属性动画集合的机制,任何用指定事务去改变可以做动画的图层属性都不会立刻发生变化,而是当事务一旦提交的时候开始用一个动画过渡到新值。
我们把改变属性时 CALayer 自动应用的动画称作行为,当 CALayer 的属性被修改时候,它会调用 -actionForKey: 方法,传递属性的名称。剩下的操作都在 CALayer 的头文件中有详细的说明,实质上是如下几步:
-
图层首先检测它是否有委托,并且是否实现 CALayerDelegate 协议指定的
- actionForLayer:forKey方法。如果有,直接调用并返回结果。 -
如果没有委托,或者委托没有实现
-actionForLayer:forKey方法,图层接着检查包含属性名称对应行为映射的actions字典。 -
如果 actions字典 没有包含对应的属性,那么图层接着在它的
style字典接着搜索属性名。 -
最后,如果在 style 里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的
-defaultActionForKey:方法。
所以一轮完整的搜索结束之后, -actionForKey: 要么返回空(这种情况下将不会有动画发生),要么是 CAAction 协议对应的对象,最后 CALayer 拿这个结果去对先前和当前的值做动画。
于是这就解释了UIKit是如何禁用隐式动画的:每个 UIView 对它关联的图层都扮演了一个委托,并且提供了 -actionForLayer:forKey 的实现方法。当不在一个动画块的实现中, UIView 对所有图层行为返回 nil 来禁用隐式动画,但是在动画 block 范围之内,它就返回了一个非空值。
显式动画
定义显示动画的时候,我们不必定义CALayer的变化,也不必执行它,而是通过CABasicAnimation逐个定义动画,其中每个动画都含有各自的属性,然后通过addAnimation:方法添加到图层中
CAAnimationDelegate在任何头文件中都找不到,但是可以在CAAnimation头文件或者苹果开发者文档中找到相关函数。在这个例子中,我们用- animationDidStop: finished: 方法在动画结束之后来更新图层backgroundColor的。
当更新属性的时候,我们需要设置一个新的事务,并且禁用图层行为。否则动画会发生两次,一个是因为显式的 CABasicAnimation ,另一次是因为隐式动画,具体实现代码如下。
- (void)viewDidLoad {
[super viewDidLoad];
CALayer *colorLayer = [CALayer layer];
colorLayer.frame = CGRectMake(0, 0, 100, 100);
colorLayer.position = CGPointMake(self.view.bounds.size.width * 0.5, self.view.bounds.size.height * 0.5);
colorLayer.backgroundColor = [UIColor redColor].CGColor;
self.colorLayer = colorLayer;
[self.view.layer addSublayer:colorLayer];
}
- (IBAction)changeColor:(id)sender {
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
//create a basic animation
CABasicAnimation *baseAnimation = [CABasicAnimation animation];
baseAnimation.keyPath = @"backgroundColor";
baseAnimation.toValue = (__bridge id)color.CGColor;
baseAnimation.delegate = self;
[self.colorLayer addAnimation:baseAnimation forKey:nil];
}
- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag{
[CATransaction begin];
[CATransaction setDisableActions:true];
[CATransaction setDisableActions:0.25];
self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue;
[CATransaction commit];
}
transform在放大两倍的时候,它的frame和bounds是如何变化的?
frame是一个复合属性,由center、bounds和transform共同计算而来。transform改变,frame会受到影响,但是center和bounds不会受到影响。也就是你使用transform来缩放,bounds是不会变的。那么由center和bounds计算得到的frame是永远保持transform为identity时的状态。这也是为什么把transform设为identity后,view会回归到最初状态。
我有一个可变容器(数组),我对外暴露了增删改查的逻辑,如何实现这个可变容器是线程安全的?
同步串行队列
NSOperationQueue和GCD的区别
-
GCD是纯C语言的API 。NSOperationQueue是基于GCD的OC的封装。
-
GCD只支持FIFO队列,NSOperationQueue可以方便设置执行顺序,设置最大的并发数量。
-
NSOperationQueue可是方便的设置operation之间的依赖关系,GCD则需要很多代码。
-
NSOperationQueue支持KVO,可以检测operation是否正在执行(isExecuted),是否结束(isFinished),是否取消(isCanceled)
-
GCD的执行速度比NSOperationQueue快。
我现在有100个任务,现在我想启用5个线程,然后并发的去把100的任务做完,设计一个线程池?
- NSOperationQueue设置最大的并发数量
- GCD信号量 使用信号量有什么需要注意的地方?
- GCD调度组
- 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");
}
信号量不可以
-(**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);
});
}
栅栏函数也不可以
- (**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]);
});
}
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 值改变。
- 当线程调用 shareInstance,此时 onceToken = 0,调用 block 中的代码,此时 onceToken 的值变为 140734537148864。
- 当其他线程再调用 shareInstance 方法时,onceToken 的值已经是 140734537148864 了,线程阻塞。
- 当 block 线程执行完 block 之后,onceToken 变为 -1,其他线程不再阻塞,跳过 block。
- 下次再调用 shareInstance 时,block 已经为 -1,直接跳过 block。
我在屏幕上点击了之后,它在屏幕上是怎么传递的?它的响应过程?
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
讲一下 Socket 长连接,比如说长连接建立的过程...
有的时候,我们需要服务端主动往客户端进行推送服务的时候,这个时候长连接就起作用了。苹果提供的push服务apns就是典型的长连接的应用,IM即时通讯应用、订单消息推送这些也是长连接的典型应用。长连接的特点是一旦通过三次握手建立链接之后,该条链路就一直存在,而且该链路是一种双向的通行机制,适合于频繁的网络请求,避免Http每一次请求都会建立链接和关闭链接的操作,减少浪费,提高效率。
socket翻译为套接字,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。它不属于OSI七层协议,它只是对于TCP,UDP协议的一套封装,让我们开发人员更加容易编写基于TCP、UDP的应用。
// 1、 创建socket
/**
参数
domain: 协议域,AF_INET --> IPV4
type: Socket 类型, SOCK_STREAM(TCP)/SOCKET_DGRAM(报文 UDP)
protocol: IPPROTO_TCP,如果传入0,会自动根据第二个参数,选择合适的协议
返回值
socket
*/
intclientSocket = socket(AF_INET, SOCK_STREAM, 0);
// 2、 连接到服务器
/**
参数
1> 客户端socket
2> 指向数据结构sockaddr的指针,其中包括目的端口和IP地址
3> 结构体数据长度
返回值
0 成功/其他 错误代号
*/
structsockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
//端口
serverAddr.sin_port = htons(12345);
//地址
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
intconnResult = connect(clientSocket, (conststructsockaddr *)&serverAddr, sizeof(serverAddr));
if(connResult == 0) {
NSLog(@"连接成功");
}else{
NSLog(@"连接失败 %zi",connResult);
return;
}
// 3、发送数据到服务器
/**
参数
1> 客户端socket
2> 发送内容地址
3> 发送内容长度
4> 发送方式标志,一般为0
返回值
如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR
*/
NSString*sendMsg = @"Hello";
ssize_t sendLen = send(clientSocket, sendMsg.UTF8String, strlen(sendMsg.UTF8String), 0);
NSLog(@"发送了 %zi 个字节",sendLen);
// 4、 从服务器接受数据
/**
参数
1> 客户端socket
2> 接受内容缓冲区地址
3> 接受内容缓冲区长度
4> 接收方式,0表示阻塞,必须等待服务器返回数据
返回值
如果成功,则返回读入的字节数,失败则返回SOCKET_ERROR
*/
uint8_t buffer[1024];//将空间准备出来
ssize_t recvLen = recv(clientSocket, buffer, sizeof(buffer), 0);
NSLog(@"接收到了 %zi 个字节",recvLen);
NSData*data = [NSDatadataWithBytes:buffer length:recvLen];
NSString*str = [[NSStringalloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"接收到数据为 %@",str);
// 5、 关闭
close(clientSocket);
TCP I/O 复用模型
当有新的客户端请求时,服务端进程会创建一个子进程,用于处理和客户端的连接和处理客户端的请求。这是一种并发处理客户端请求的方案,但并不是一个很好的方案,因为创建进程时需要付出很大的代价,需要大量的运算和内存空间,由于每个进程都具有独立的内存空间,所以相互间的数据交换也要求采用相对复杂的方法(IPC属于相对复杂的通信方法)
那么有没有其他的方案可以在不创建子进程的前提下可以并发处理客户端请求?当然是有的,那就是I/O复用技术了。I/O多路复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作
多线程的方式
- pthread
- NSThread
- NSOperationQueue
- GCD
线程同步的方式
锁: synchronized 、 NSLock(lock/unlock)、 NSRecursiveLock递归锁 ...
信号量:dispatch_semaphore
synchronized
SyncData结构:
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr<objc_object> object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;
objc_sync_enter关键流程分析:
- 获取当前线程的缓存链表结构,查看缓存链表是否存在对象object
- 如果存在,则执行lockCount++,更新缓存并结束流程
- 如果当前线程的缓存链表中未找到对象object缓存,则查看listp总链表结构
- 若总链表结构存在对象object,则threadCount++
- 若总链表结构不存在对象object,则新建一个SyncData,且将lockCount、threadCount置为1,最后更新缓存
objc_sync_exit关键流程分析:
- 获取当前线程的缓存链表结构,查看缓存链表是否存在对象object
- 如果存在,则执行lockCount--
- 如果当前的lockCount==0,则threadCount--,更新缓存并结束流程
- lockCount的处理保证能够递归调用
- threadCount保证了线程的等待,只需要系统底层发送消息唤醒线程,不造成死锁现象!
总结: @synchronized是一把支持多线程递归的互斥锁。
常用的第三方库都有哪些?有没有看过源码?
MVVM 和 MVC 的区别
常用的设计模式
runtime 的使用场景? 分类能不能添加属性,分类添加的关联对象是添加到哪里去了? (是添加到isa里面了吗?)
使用场景
- 给系统分类添加属性、方法
- 方法交换
- 获取对象的属性、私有属性
- 字典转换模型
- KVC、KVO
- 归档(编码、解码)
- NSClassFromString class<->字符串
- block
- 类的自我检测
关联对象
关联对象是以哈希表的格式,存储在一个全局单例中
isa 的结构
联合体
| 成员 | 位 | 含义 |
|---|---|---|
| nonpointer | 1bit | 表示是否对 isa 指针开启指针优化。 0:纯 isa 指针;1:不止是类对象地址。isa 中包含了类信息、对象的引用计数等 |
| has_assoc | 1bit | 标志位: 表明对象是否有关联对象。0:没有;1:存在。没有关联对象的对象释放的更快 |
| has_cxx_dtor | 1bit | 标志位: 表明对象是否有C++或ARC析构函数。没有析构函数的对象释放的更快 |
| shiftcls | 33bit | 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。 |
| magic | 6bit | 用于调试器判断当前对象是真的对象还是没有初始化的空间 , 固定为 0x1a |
| weakly_referenced | 1bit | 标志位:用于表示该对象是否被别ARC对象弱引用或者引用过。没有被弱引用的对象释放的更快 |
| deallocating | 1bit | 标志位: 用于表示该对象是否正在被释放 |
| has_sidetable_rc | 1bit | 标志位: 用于标识是否当前的引用计数过大 ( 大于 10 ) ,无法在 isa 中存储,则需要借用sidetable来存储,标志是否有外挂的散列表 |
| extra_rc | 19bit | 实际上是对象的引用计数减 1 . 比如,一个 object 对象的引用计数为7,则此时 extra_rc 的值为 6 |
swift 可选型 optional 是枚举、结构体、还是class?
多了一位来表示,枚举
swift 怎么解包
- iflet
- guardlet
- 强制解包
swift inout 的作用
可变参数
swift 结构体和类 的区别
- class 引用类型
- struct 值类型
结构体里面修改一个属性的值是如何修改的? class直接用点语法就可以改变
@mutating
排序算法 - 快排 思路 时间复杂度 空间复杂度
一张图片加载到屏幕上经历了哪些过程?
1w*1w的图片如何不加载到内存当中进行显示?
100100的图片加载到5050的uiimageView中,是如何检测出来?
OC的数组可以存储结构体吗?
struct不是对象
崩溃第三方工具的实现原理
math崩溃
埋点
hook生命周期的方法
LRU
子线程的runloop是做什么的?也是用来处理事件的吗?系统使用runloop使用了哪些功能?(NSTimer)
多线程的并发方式都了解哪些?
GCD
NSOperationQueue 优先级和依赖关系 有什么关系?
NSOperation 能终止一个任务的执行吗?