iOS -- 问题杂记

本篇内容不做为任何题目的解答,仅仅是我的学习记录,若有错误还请指正。算法

iOS基础必备

1:讲讲你对noatomic & nonatomic的理解

atomicseter/getter内部实现是用了互斥锁来保证seter/getter在多线程中的安全,但atomic修饰的对象是自定义的,可能并无加锁,在多线程中atomic修饰对象并不能保证线程安全。编程

nonatomicsetter/getter方法的实现并无加互斥锁,因此nonatomic修饰的对象是非线程安全的,同时nonatomicsetter/getter方法也是非线程安全的,但也正由于没有互斥锁因此性能要比atomic好。swift

(举例:当多个线程同时调用同一属性的读取方法时,线程1会get到一个肯定的值,可是get的值不可控,多是线程2或者线程3...set以后的值,也多是以前的值)数组

2:被 weak 修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable 么?里面的结构能够画出来么?

weak修饰的对象在被释放时候会被自动置为nil缓存

runtime维护了一个weak表,用于存储指向某个对象的全部weak指针。weak表实际上是一个hash哈希表,Key是所指对象的地址,Valueweak指针的地址数组(这个地址的值是所指对象指针的地址)。安全

一、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。bash

二、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak()的做用是更新指针指向,建立对应的弱引用表。服务器

三、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取全部weak指针地址的数组,而后遍历这个数组把其中的数据设为nil,最后把这个entryweak表中删除,最后清理对象的记录。网络

runtime内存空间中,SideTables是一个64个元素长度的hash数组,里面存储了SideTableSideTableshash键值就是一个对象objaddress。 所以能够说,一个obj,对应了一个SideTable。可是一个SideTable,会对应多个obj。由于SideTable的数量只有64个,因此会有不少obj共用同一个SideTable。而在一个SideTable中,又有两个成员,分别是RefcountMap refcnts(引用计数的 hash表,其keyobj的地址,而value,则是obj对象的引用计数)和weak_table_t weak_tableweak引用全局hash表),weak_table则存储了弱引用obj的指针的地址,其本质是一个以obj地址为key,弱引用obj的指针的地址做为valuehash表。hash表的节点类型是weak_entry_t多线程

3:block 用什么修饰?strong 能够?

由于block变量默认是声明为栈变量的,为了可以在block的声明域外使用,因此要把block copy到堆,因此说为了block属性声明和实际的操做一致,blockcopy修饰。

对于strong修饰符:

首先,在OC中的三种类型的block

  • _NSConcreateGlobalBlock 全局的静态block,不会访问任何外部变量。
  • _NSConcreateStackBlock 保存在栈中的block,当函数返回时会被销毁。
  • _NSConcreateMallocBlock 保存在堆区的block,当引用计数为0时被销毁。

MRC环境下,block存放在全局区、堆区、栈区,ARC环境下只存放在全局区、堆区(栈区block,在ARC状况下会自动拷贝到堆区)。

因此在MRC环境下,若是block要访问外部变量,就必须用copy修饰,而在ARC环境下,若是block访问了外部变量,系统会自动将栈区block拷贝到堆区,因此也可使用strong。只不过copyMRC遗留下来的,习惯而已

4:block 为何可以捕获外界变量? __block作了什么事?

block中使用到的局部变量,都会在编译时动态建立的block结构体中建立一个与局部变量名称同样的实例变量,该实例变量存储着外部的局部变量的值,而当执行block时,再将这里存储下来的值取出来。

__block所起到的做用就是只要观察到该变量被block所持有,就将外部变量在栈中的内存地址拷贝堆中。__block将变量包装成对象,而后在把捕获到的变量封装在block的结构体里面,block内部存储的变量为结构体指针,也就能够经过指针找到内存地址进而修改变量的值。

5:谈谈你对事件的传递链和响应链的理解

传递链:

(1)发生触摸事件后,系统会利用Runloop将该事件加入到一个由UIApplication管理的队列事件中.

(2)UIApplication会从事件队列中取出最前面的事件,并将事件分发下去处理,发送事件给应用程序的主窗口UIWindow.

(3)主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件

(4)遍历找到合适的视图控件后,就会调用视图控件的touches方法来处理事件:touchesBegin...此子控件就是咱们须要找到的第一响应者

响应链:

(1)判断第一响应者可否响应事件,若是第一响应者能进行响应,则事件在响应链中的传递终止。若是第一响应者不能响应,则将事件传递给 nextResponder也就是一般的superview进行事件响应

(2)若是事件继续上传至UIWindow而且没法响应,它将会把事件继续上报给UIApplication

(3)若是事件继续上报至UIApplication而且也没法响应,它将会将事件上报给其Delegate

(4)若是最终事件依旧未被响应则会被系统抛弃

注: 穿透事件,扩大响应区域。

6:谈谈 KVC 以及 KVO 的理解?

KVC(key-value-coding)键值编码,是一种间接访问实例变量的方法。提供一种机制来间接访问对象的属性。

一、给私有变量赋值。

二、给控件的内部属性赋值(如自定义UITextFiledclearButton,或placeholder的颜色,通常可利用runtime获取控件的内部属性名,Ivar *ivar = class_getInstanceVariable获取实例成员变量)。

[textField setValue:[UIColor redColor] forKeyPath:@"placeholderLabel.textColor"];
复制代码

三、结合Runtimemodel和字典的转换(setValuesForKeysWithDictionaryclass_copyIvarList获取指定类的Ivar成员列表)

KVO是一种基于KVC实现的观察者模式。当指定的被观察的对象的属性更改了,KVO会以自动或手动方式通知观察者。

监听 ScrollViewcontentOffSet属性

[scrollview addObserver:self forKeyPath:@"contentOffset"  options:NSKeyValueObservingOptionNew context:nil];
复制代码

7:RunLoop 的做用是什么?它的内部工做机制了解么?

RunLoop基本做用:

(1).使程序一直运行并接受用户输入:

程序一启动就会开一个主线程,主线程一开起来就会跑一个主线程对应的RunLoopRunLoop保证主线程不会被销毁,也就保证了程序的持续运行。

(2)决定程序在什么时候应该处理哪些Event

好比:touches事件,timer事件,selector事件

(3)节省CPU时间 程序运行起来,没有任何事件源的时候,RunLoop会告诉CPU,要进入休眠状态,这时CPU就会将其资源释放出来去作其余的事情,当有事件源来的时候RunLoop就会被唤醒处理事件源。

8:苹果是如何实现 autoreleasepool的?

autoreleasepool以一个队列数组的形式实现,主要经过下列三个函数完成: (1).objc_autoreleasepoolPush (2).objc_autoreleasepoolPop (3).objc_autorelease

经过函数名就能够知道,对autorelease分别执行push,和pop操做。销毁对象时执行release操做。

iOS -- Autorelease & AutoreleasePool

9:谈谈你对 FRP (函数响应式) 的理解

函数响应式编程--rxswift

10:平时开发有没有玩过 Instrument ?

Instruments里面工具不少,经常使用的有:

(1).Time Profiler:性能分析,用来检测应用CPU的使用状况.能够看到应用程序中各个方法正在消耗CPU时间。

(2).Zoombies:检查是否访问了僵尸对象,可是这个工具只能从上往下检查,不智能

(3).Allocations:用来检查内存,写算法的那批人也用这个来检查

(4).Leaks:检查内存,看是否有内存泄漏

(5).Core Animation:评估图形性能,这个选项检查了图片是否被缩放,以及像素是否对齐。被放缩的图片会被标记为黄色,像素不对齐则会标注为紫色。黄色、紫色越多,性能越差。

Runtime

1:什么是 isa,isa 的做用是什么?

2:一个实例对象的isa 指向什么?类对象指向什么?元类isa 指向什么?

是一个Class类型的指针.

每一个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从自己查找类方法的实现,若是没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向自己,这样造成了一个封闭的内循环。

3:objc 中类方法和实例方法有什么本质区别和联系?

类方法:

(1).类方法是属于类对象的

(2).类方法只能经过类对象调用

(3).类方法中的self是类对象

(4).类方法能够调用其余的类方法

(5).类方法中不能访问成员变量

(6).类方法中不定直接调用对象方法

实例方法:

(1).实例方法是属于实例对象的

(2).实例方法只能经过实例对象调用

(3).实例方法中的self是实例对象

(4).实例方法中能够访问成员变量

(5).实例方法中直接调用实例方法

(6).实例方法中也能够调用类方法(经过类名)

4:load 和 initialize 的区别?

共同点:

(1).+load+initialize会被自动调用,不能手动调用它们。

(2).子类实现了+load+initialize的话,会隐式调用父类的+load+initialize方法

(3).+load+initialize方法内部使用了锁,所以它们是线程安全的。

不一样点:

+load方法是在runtime加载类、分类的时候调用(根据函数地址直接调用,默认执行),每一个类,分类的+load方法都只会调用一次。

先执行父类的+load方法,再执行子类的+load方法,而分类的+load方法会在它的主类的+load方法以后执行。(分类按照编译顺序前后调用)

+initialize方法在第一次给某个类发送消息时调用(经过objc_msgSend调用),而且只会调用一次,是懒加载模式,此时全部的类都已经加载到了内存中,若是这个类一直没有使用,就不会调用+initialize方法。

先执行父类的+initialize方法,再执行子类的+initialize方法,若是子类没有实现+initialize方法时,会调用父类的+initialize方法,因此父类的+initialize方法会调用屡次,若是分类实现了+initialize方法,就会'覆盖掉'类自己的+initialize方法。

+load方法通常是用来交换方法Method Swizzle+initialize方法主要用来对一些不方便在编译期初始化的对象进行赋值,或者说对一些静态常量进行初始化操做

5:_objc_msgForward 函数是作什么的?直接调用会发生什么问题?

_objc_msgForwardIMP 类型(函数指针),用于消息转发的:当向一个对象发送一条消息,但它并无实现的时候,_objc_msgForward会尝试作消息转发。

_objc_msgForward消息转发作的几件事:

(1).调用resolveInstanceMethod:方法 (或 resolveClassMethod:)。容许用户在此时为该 Class动态添加实现。若是有实现了,则调用并返回YES,那么从新开始objc_msgSend流程。这一次对象会响应这个选择器,通常是由于它已经调用过class_addMethod。若是仍没实现,继续下面的动做。

(2).调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。若是获取到,则直接把消息转发给它,返回非 nil对象。不然返回nil,继续下面的动做。注意,这里不要返回self,不然会造成死循环。

(3).调用methodSignatureForSelector:方法,尝试得到一个方法签名。若是获取不到,则直接调用doesNotRecognizeSelector抛出异常。若是能获取,则返回非nil:建立一个NSlnvocation并传给forwardInvocation:

(4).调用forwardInvocation:方法,将第3步获取到的方法签名包装成 Invocation传入,如何处理就在这里面了,并返回非nil

(5).调用doesNotRecognizeSelector: ,默认的实现是抛出异常。若是第3步没能得到一个方法签名,执行该步骤。

一旦调用_objc_msgForward,将跳过查找IMP 的过程,直接触发“消息转发”,若是调用了_objc_msgForward,即便这个对象确实已经实现了这个方法,也会告诉objc_msgSend没有找到这个方法的实现。最多见的场景是:你想获取某方法所对应的NSInvocation对象。

6:简述下 Objective-C 中调用方法的过程

先来看一下Method在头文件中的定义:

typedef struct objc_method *Method;

struct objc_method {
    SEL method_name;
    char * method_types;
    IMP method_imp;
};
复制代码

Method被定义为一个objc_method指针,在 objc_method结构体中,包含一个 SEL 和一个IMP ,其中,SEL是一个指向objc_selector 的指针,其实就是一个保存方法名的字符串。IMP是一个“函数指针”,就是用来找到函数地址,而后执行函数。因此,Method创建了SELIMP 的关联,当对一个对象发送消息时,会经过给出的SEL 去找到IMP,而后执行。

再加上继承的状况,能够总结出:当向一个对象发送消息时,会去这个类的方法缓存里寻找(cache methodLists)若是有缓存则跳到方法实现,不然继续在这个类的 methodLists中查找相应的SEL,若是查不到,则经过super_class 指针找到父类,再去父类的缓存列表和方法列表里查找,层层递进,直到找到基类为止。最后仍然找不到,才会走消息转发的流程。

7:可否想向编译后获得的类中增长实例变量?可否向运行时建立的类中添加实例变量?为何?

不能向编译后获得的类中增长实例变量;能向运行时建立的类中添加实例变量;

由于编译后的类已经注册在runtime 中,类结构体中的objc_ivar_list实例变量的链表 和instance_size 实例变量的内存大小已经肯定,同时runtime会调用 class_setIvarLayoutclass_setWeakIvarLayout来处理strong weak引用。因此不能向存在的类中添加实例变量;

运行时建立的类是能够添加实例变量,调用 class_addIvar函数。可是得在调用 objc_allocateClassPair 以后,objc_registerClassPair以前。

8:谈谈你对切面编程的理解

AOP: Aspect Oriented Programming 面向切面编程,经过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术 AOPOOP 的延续,函数式编程的一种衍生范型。(利用 Objective-C Runtime 的黑魔法Method Swizzling。)

优点:对业务逻辑的各个部分进行隔离,下降业务逻辑各部分之间的耦合度,提升程序的可重用性,提升了开发的效率。

漫谈iOS AOP编程之路

iOS面向切面编程AOP实践

网络&多线程

1:HTTP的缺陷是什么?

(1).通讯过程当中使用的是未加密的明文,内容会被取抓。

(2).对于服务器或者客户端来讲,不会验证通讯方的身份,所以有可能遭到中间人的假装。

(3).没法验证所发送报文的完整性和安全性,并且报文的内容也有多是中间人篡改以后发送过来的。

2:谈谈三次握手,四次挥手!为何是三次握手,四次挥手?

iOS:为何TCP链接要三次握手,四次挥手

3:socket 链接和 Http 链接的区别

HTTP协议是基于TCP链接的,是应用层协议,主要解决如何包装数据。HTTP链接是一种短链接,客户端向服务器发送一次请求,服务器响应后链接断开,节省资源,并且服务器不能主动给客户端响应。

Socket是对TCP/IP协议的封装,Socket自己并非协议,而是一个调用接口(API),经过Socket,咱们才能使用TCP/IP协议。Socket链接是一种长链接,客户端跟服务器端直接使用Socket进行链接,没有规定链接后断开,所以客户端和服务器段保持链接通道,双方能够主动发送数据。Socket默认链接超时时间是30秒,默认大小是8K(理解为一个数据包大小)。

4:HTTPS,安全层除了SSL还有,最新的? 参数握手时首先客户端要发什么额外参数

5:HTTPS是什么?握手过程,SSL原理,非对称加密了解多少

HTTPS解析

6:何时POP网路,有了 Alamofire 封装网络 URLSession为何还要用Moya ?

POP:面向协议编程 面向协议 = 协议 + 扩展 + 继承 经过协议、扩展作功能划分,下降模块间的耦合,加强代码的可扩展性。iOS中有一个不足之处就是多重继承,而协议正好可以解决多重继承的问题。在Swift中结构体变的更增强大了,不只能定义属性,还能定义方法,还能多重继承协议,这是OC所不提供的。

若是已经使用Alamofire进行抽象URLSession,为何不使用某些方法来抽象URL,参数等的实质呢,所以,Moya的基本思想是,咱们须要一些网络抽象层,以充分封装实际直接调用Alamofire的层。

7:如何实现 dispatch_once

dispatch_once能保证任务只会被执行一次,即便同时多线程调用也是线程安全的。经常使用于建立单例、swizzeld method等功能。

@implementation ZHClass

+ (id)sharedInstance {
    static ZHClass *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}
复制代码

8:可否写一个读写锁?谈谈具体的分析

信号量方式:

- (void)viewDidLoad {
	[super viewDidLoad];
	// Do any additional setup after loading the view.
	self.semaphore = dispatch_semaphore_create(1);
	
	for (NSInteger i = 0; i < 10; i ++) {
		[[[NSThread alloc]initWithTarget:self selector:@selector(read) object:nil]start];
		[[[NSThread alloc]initWithTarget:self selector:@selector(write) object:nil]start];
	}
}

- (void)read{
	dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
	NSLog(@"%s",__func__);
	dispatch_semaphore_signal(self.semaphore);
}
- (void)write{
	dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
	NSLog(@"%s",__func__);
	dispatch_semaphore_signal(self.semaphore);
}
复制代码

pthread_rwlock_t 方式:

@property (nonatomic,assign) pthread_rwlock_t rwlock;

//初始化读写锁
pthread_rwlock_init(&_rwlock, NULL);
	
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
	for (NSInteger i = 0; i < 3; i ++) {
		dispatch_async(queue, ^{
			[[[NSThread alloc]initWithTarget:self selector:@selector(readPthreadRWLock) object:nil]start];
			[[[NSThread alloc]initWithTarget:self selector:@selector(writePthreadRWLock) object:nil]start];
		});
	}
	
//读
- (void)readPthreadRWLock{
    pthread_rwlock_rdlock(&_rwlock);
    NSLog(@"读文件");
    sleep(1);
    pthread_rwlock_unlock(&_rwlock);
}
// 写
- (void)writePthreadRWLock{
    pthread_rwlock_wrlock(&_rwlock);
    NSLog(@" 写入文件");
    sleep(1);
    pthread_rwlock_unlock(&_rwlock);
}

//销毁锁
- (void)dealloc{
	pthread_rwlock_destroy(&_rwlock);
}
复制代码

dispatch_barrier方式:

//异步队列
self.testqueue = dispatch_queue_create("rw.thread", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 5; i ++) {
	dispatch_async(queue, ^{
		[self readBarryier];
		[self readBarryier];
		[self readBarryier];
		[self writeBarrier];
	});
}

- (void)readBarryier{
	dispatch_async(self.testqueue, ^{
		NSLog(@"读文件 %@",[NSThread currentThread]);
		sleep(2);
	});
}
- (void)writeBarrier{
	dispatch_barrier_async(self.testqueue, ^{
		NSLog(@"写入文件 %@",[NSThread currentThread]);
		sleep(1);
	});
}
复制代码

9:何时会出现死锁?如何避免?

死锁是指两个或两个以上的进程在执行过程当中,因为竞争资源或者因为彼此通讯而形成的一种阻塞的现象,若无外力做用,它们都将没法推动下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。是操做系统层面的一个错误,是进程死锁的简称。

死锁的产生知足一些特定条件:

(1).互斥条件:进程对于所分配到的资源具备排它性,即一个资源只能被一个进程占用,直到被该进程释放。

(2).请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已得到的资源保持不放。

(3).不剥夺条件:任何一个资源在没被该进程释放以前,任何其余进程都没法对他剥夺占用。

(4).循环等待条件:当发生死锁时,所等待的进程一定会造成一个环路(相似于死循环),形成永久阻塞。

10:有哪几种锁?各自的原理?它们之间的区别是什么?最好能够结合使用场景来讲

(1).NSLock实现了最基本的互斥锁,遵循了 NSLocking协议,经过lockunlock 来进行锁定和解锁。

(2).NSRecursiveLock递归锁,能够被一个线程屡次得到,而不会引发死锁。它记录了成功得到锁的次数,每一次成功的得到锁,必须有一个配套的释放锁和其对应,这样才不会引发死锁。只有当全部的锁被释放以后,其余线程才能够得到锁

(3).NSCondition 是一种特殊类型的锁,经过它能够实现不一样线程的调度。一个线程被某一个条件所阻塞,直到另外一个线程知足该条件从而发送信号给该线程使得该线程能够正确的执行。

(4).NSConditionLock 对象所定义的互斥锁能够在使得在某个条件下进行锁定和解锁。它和 NSCondition 很像,但实现方式是不一样的。

(5).pthread_mutex,互斥锁是一种超级易用的互斥锁,使用的时候,只须要初始化一个pthread_mutex_tpthread_mutex_lock来锁定 pthread_mutex_unlock 来解锁,当使用完成后,记得调用 pthread_mutex_destroy 来销毁锁。

(6).pthread_rwlock,读写锁,在对文件进行操做的时候,写操做是排他的,一旦有多个线程对同一个文件进行写操做,后果不可估量,但读是能够的,多个线程读取时没有问题的。

当读写锁被一个线程以读模式占用的时候,写操做的其余线程会被阻塞,读操做的其余线程还能够继续进行。 当读写锁被一个线程以写模式占用的时候,写操做的其余线程会被阻塞,读操做的其余线程也被阻塞。

(7).dispatch_semaphore,信号量机制实现锁,等待信号,和发送信号,当有多个线程进行访问的时候,只要有一个得到了信号,其余线程的就必须等待该信号释放。

(8).@synchronized,一个便捷的建立互斥锁的方式,它作了其余互斥锁所作的全部的事情。

应当针对不一样的操做使用不一样的锁:

当进行文件读写的时候,使用pthread_rwlock 较好,文件读写一般会消耗大量资源,而使用互斥锁同时读文件的时候会阻塞其余读文件线程,而 pthread_rwlock不会。 当性能要求较高时候,可使用pthread_mutex或者 dispath_semaphore,因为OSSpinLock 不能很好的保证线程安全,而在只有在iOS10中才有 os_unfair_lock ,因此,前两个是比较好的选择。既能够保证速度,又能够保证线程安全。 对于NSLock及其子类,速度来讲NSLock < NSCondition < NSRecursiveLock < NSConditionLock

持续更新中..........

相关文章
相关标签/搜索