More Stronger

笔记1

2017-03-05  本文已影响14人  allen852

1.@property 后面可以有哪些修饰符

第一种:控制set方法的内存管理:retain strong assign weak copy

第二种:控制需不需要生成set方法:readwrite(默认),readonly

第三种:线程安全:atomic nonatomic

第四种:控制set方法和get方法的名称 setter getter

2.什么情况使用 weak 关键字,相比 assign 有什么不同

相同点是:它们都是弱引用声明类型

最大不同:当他们所指向的对象被释放了,weak修饰的属性会被赋值nil,而assign不会,从而可能导致访问野指针的问题。所以一般来说使用weak修饰delegate更为安全。除此之外weak常用语UI控件,防止循环引用时使用;而assign常用语基本数据类型,枚举,结构体。

3.怎么用 copy 关键字?

在对属性使用setter方法赋值的时候,会使用copy拷贝一份在进行赋值,从而让赋值者和被赋值者指向不同对象,修改互不影响。一般使用在不可变类型数据如NSString,NSArray,NSDictionary,修饰block时候可以将其复制到堆上。

4.这个写法会出什么问题:@property (copy) NSMutableArray *array;

通过copy修饰使用stter方法赋值时会copy一份,这是返回的是不可变类型数据,对其进行增删改更新操作就会报错。

5.如何修改一个readonly修饰的属性

当我们声明一个 readonly 的属性,外部可能会通过 KVC 修改该属性值。

使用KVC赋值流程:首先 setKey方法,如果找不到,就检查类的+ (BOOL)accessInstanceVariablesDirectly 返回 YES,就继续查找成员变量,否则调用setValue:forUndefinedKey。

成员变量调用顺序,首先是带下划线的_key , _isKey ,然后不带下划线的 key , isKey,如果都没有调用setValue:forUndefinedKey,该方法默认是抛出异常。

如何避免 KVC 修改属性值,须将定义属性所在类的类方法+ (BOOL)accessInstanceVariablesDirectly 重写,使其返回 NO.

6.如何让自定义的类用 copy 修饰符

令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopying与NSMutableCopying协议

- (id)copyWithZone:(NSZone*)zone {    

CYLUser *copy = [[[self class] allocWithZone:zone] initWithName:_name age:_age sex:_sex];

return copy;

}

7.@property 的本质是什么

当我们使用property创建属性,系统会自动为我们生成:带下划线的实例变量,根据修饰符生成对应的setter getter方法。

8.@protocol 和 category 中如何使用 @property

要使用类别添加属性,需要结合runtime运行时机制里的关联属性。

在protocol中添加property时,其实就是声明了 getter 和 setter 方法,在实现这个protocol协议的类中,我们要自己手动添加实例变量,并且需要实现setter/getter方法

9.runtime 如何实现 weak 属性

对于 weak 变量会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象不再持有强引用被释放,根据对象内存地址是找到对应的 weak 变量,从而赋值为 nil

10.weak属性需要在dealloc中置nil么

weak弱应用并不会持有对象,当对象并释放后,weak会自动置nil

11.不显式指定任何属性关键字时,默认的关键字都有哪些

atomic,readwrite

基本数据类型assign

OC对象 strong

12.用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题

使用strong会导致指向同一个对象,修改会互相影响。但是如果使用不可变得类型对他们进行赋值,例如使用NSArray 赋值给 NSArray 的属性,因此在运行过程中不可变类型是不会改变的,那么strong copy效果一样,都是指向同一对象。

13.可变不可变类型的copy与mutableCopy

[immutableObject copy]//浅复制(NSArray,NSString)

[immutableObjectmutable Copy]//深复制

[mutableObject copy]//深复制(NSMutableArray,NSMutableString)

[mutableObjectmutable Copy]//深复制

14.@synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?

1.如果指定了成员变量的名称,会生成一个指定的名称的成员变量

2.如果没有指定成员变量的名称会自动生成一个属性同名的成员变量

3.如果这个成员变量已经存在了就不再自动生成了

15.objc中向一个nil对象发送消息将会发生什么

当对nil发送消息的时候,程序并不会报错,因为对对象发送消息在运行的时候实际调用了objc_msgSend方法,第一个参数是self对象,第二个是SEL类型的数据,这个时候第一个参数self是空的,所以是不会去执行任何东西直接返回,如果方法返回值是一个对象则返回Nil,返回值为结构体或者基本类型返回 0。

16.什么时候会报unrecognized selector的异常

1.运行时调用objc_msgSend方法,根据对象的isa指针找到类的分发表,根据SEL和IMP映射关系寻找该方法,如果找不到就去父类找,如果依然找不到就会调用 objc_msgForward 进行消息转发,如果转发失败,抛出异常。

17.一个objc对象如何进行内存布局

1.包含所有父类和自己的成员变量

2.持有一个isa指针,指向类的数据结构体(包含分发表和指向父类的指针)

18.runtime如何通过selector找到对应的IMP地址

IMP:一个函数指针,指向了方法的地址

SEL:相当于方法编号

通过selector 返回的SEL类型数据,在类所维护的分发表下,我们就可以找到对应的IMP指针。

每一个继承于NSObject的类都能自动获得runtime的支持。在这样的一个类中,有一个isa指针,指向该类定义的数据结构体,这个结构体是由编译器编译时为类(需继承于NSObject)创建的.在这个结构体中有包括了指向其父类类定义的指针以及Dispatch table. Dispatch table是一张SEL和IMP的对应表

19.objc中的类方法和实例方法有什么本质区别和联系

类方法是属于类对象的 实例方法是属于实例对象的

类方法只能通过类对象调用 实例方法只能通过实例对象调用

类方法中不能访问成员变量 类方法中不能直接调用对象方法 实例方法中也可以调用类方法(通过类名)

20._objc_msgForward函数是做什么的,直接调用它将会发生什么

_objc_msgForward是 IMP 类型,用于消息转发的

当向一个对象发送一条消息,但在本类和父类中都查找不到方法,_objc_msgForward会尝试做消息转发

首先它会尝试去寻找在运行的时候是否动态地去实现了这个方法,如果找到了就冲走objc_msgSend的流程去调用方法,如果没有就去尝试将消息转发给其他的能响应该消息的对象,如果还没有处理掉就会跑出异常


一旦调用_objc_msgForward,将跳过去类和父类查找 IMP 的过程,直接触发“消息转发”。

IMP(id, SEL, ...)

21.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量

编译后的类已经注册在 runtime 中,类结构体中存放实例变量的链表大小已经确定,不能添加。

运行时创建的类是可以添加实例变量,调用 class_addIvar 函数

26.ARC通过什么方式帮助开发者管理内存

在编译期和运行期两部分去管理内存

首先在编译期,ARC用的是更底层的C接口实现的retain/release/autorelease,这样做性能更好,也是为什么不能在ARC环境下手动retain/release/autorelease

其次,在运行时也会去做相对应的优化。

iOS 内存管理

1.对象的引用计数器:当一个对象引用计数为0,释放;一个弱应用是不持有对象的

2.属性修饰符 :同时修饰符来控制对象被赋值时候的内存处理

3.自动释放池 : 一种对对象的延迟释放机制。当池被销毁的时候,对池内的对象发送realse消息,并且出栈。

这整个构成ARC下iOS最核心的内存管理

28.BAD_ACCESS在什么情况下出现?

访问了野指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息

29.使用block时什么情况会发生引用循环,如何解决?

一个对象直接或间接地强引用了block,在block中又强引用了该对象,就会发射循环引用。

解决方法是将该对象使用__weak或者__block修饰符修饰之后再在block中使用。

30.使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?

UIView的block版本写动画时不需要考虑(只是block强引用了当前对象,单向引用)

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

self.someProperty = xyz;//没有循环引用

}];

如果使用一些参数中可能含有 ivar 的系统 api ,如 GCD

__weak__ typeof__ (self)  weakSelf = self;

dispatch_group_async(_operationsGroup, _operationsQueue, ^{

__typeof__(self)  strongSelf = weakSelf;

[strongSelf doSomething];

[strongSelf doSomethingElse];

} );

当前对象->_operationsGroup - >block - > 当前对象self;

31.addObserver:forKeyPath:options:context:各个参数的作用分别是什么,observer中需要实现哪个方法才能获得KVO回调?

object : 被观察对象

observer: 观察对象

forKeyPath里面带上property的name,如UIView的frame、center等等

options: 有4个值,分别是:

用于控制传递的参数:

NSKeyValueObservingOptionNew 把更改之前的值提供给处理方法

NSKeyValueObservingOptionOld 把更改之后的值提供给处理方法

用于控制触发时机

NSKeyValueObservingOptionInitial 一旦注册,立马就会调用一次。

NSKeyValueObservingOptionPrior 分2次调用。在值改变之前和值改变之后。

注:例子里的0就代表不带任何参数进去

context: 上下文,用于传递参数给监听方法。

需要实现的方法:

- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context{

}

32.如何手动触发一个value的KVO

[self willChangeValueForKey:@"mj_footer"]; // KVO

objc_setAssociatedObject(self, &MJRefreshFooterKey,//修改属性

mj_footer, OBJC_ASSOCIATION_ASSIGN);

[self didChangeValueForKey:@"mj_footer"];//KVO

33.KVC的keyPath中的集合运算符如何使用?

1.用在集合对象上

2.简单集合运算符有@avg, @count , @max , @min ,@sum

3.格式 @"@sum.age"或 @"集合属性.@max.age"

34.kvo实现原理

在这里使用了 isa-swizzling 机制来实现KVO。当观察对象A时,KVO机制动态创建一个新的名为:NSKVONotifying_A的新类,该类继承自对象A的本类,且KVO为NSKVONotifying_A重写观察属性的setter方法,setter方法会负责在调用原setter方法之前和之后,而在运行的时候,对象 isa 指针会改变为指向新的类,从而可以通知所有观察对象属性值的更改情况。

35.为什么ARC中的IBOutlet 属性都推荐使用weak?

当我们在 xib 文件中添加控件,已经是当前的视图层级结构中的一部分了,指向的关系就是viewController 强引用了顶级视图 UIView,UIView 强引用了里面的子控件,因此通过 IBOutlet 将空间作为属性添加进控制器的时候,就不需要再次使用strong,使用weak就可以。

36.IB中User Defined Runtime Attributes如何使用

使用 User Defined Runtime Attributes 可以将一些需要在 ViewControllerzhong 中实现的代码转移到 interface builder 中配置。可以配置一些系统的属性,如layer.border layer.corner 也可以配置一些自定义的属性,然后再程序中通过sender.valueForKey 来获取。

37.如何调试BAD_ACCESS错误

打开 Enable Zombie Objects 选项,一个对象销毁时会被转化为_NSZombie 僵尸对象僵尸对象会显示接受到得信息,然后自动跳入调试器,得到我们想要的错误信息),当访问了一个已被释放的对象,就会明确地将错误的信息打印出来,可以让我们定位到具体代码哪里出的问题。

38.LLDB

LLDB为Xcode提供了底层调试环境,我们可以通过LLDB指令去做一些调试操作,如

1.设置断点

2.设置观察点监测一些变量

3.查看线程状态


-- 2.苹果是如何实现autoreleasepool的

autoreleasepool 是一种让对象延迟释放的机制,每个pool的管理其实是一种类似栈结构的进栈出栈操作,执行过程就是:

1.将自动释放对象通过push进栈

2.当释放池销毁,对里面的每一个对象pop出栈的同时,发送release消息


40.dispatch barrier

1.允许在一个并发队列中创建一个同步点。当在并发队列中遇到一个barrier, 他会延迟执行barrier的block,等待所有在barrier之前提交的blocks执行结束。 这时,barrier block自己开始执行。 之后, 队列继续正常的执行操作。

2.必须使用dispatch_queue_create创建的并发队列才会达到上面的效果.

例如:当前需要我们需要对缓存的数据进行读取后,根据情况进行更新操作,等更新完毕后,再把更新的数据读取出来,就可以通过barrier来实现,在 dispatch_barrier_async 里面实现更新操作的任务。


41.性能优化

1.合理的线程分配,过多线程创建会导致消耗过多

DB操作、日志记录、网络回调都在各自的固定线程

2.预处理和延时加载

对于一些需要耗费大量线程时间的操作,并且需要及时地显示在用户面前,提前放到后台线程进行计算

UITableView就是最典型的例子(对于可视区域外的cell适当地进行延迟加载)

3.缓存

cache线程安全问题,防止一边修改一边遍历的crash。

cache的根据需要建立缓存释放机制,防止缓存数据过大。

4.使用正确的API

了解imageNamed:与imageWithContentsOfFile:的差异(imageNamed:适用于会重复加载的小图片,因为系统会自动缓存加载的图片,imageWithContentsOfFile:仅加载图片)

NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"apple"ofType:@"png"];

UIImage *appleImage = [[UIImage alloc] initWithContentsOfFile:imagePath];

缓存NSDateFormatter的结果,过多地创建会影响影响性能(使用当前线程字典来保存对象,时间格式作为键值)

5.数据库优化

主要是合适地建立索引,提高查找效率,防止全表扫描。

42.block与delegate使用区别

block 使用更简单,代码也紧凑,但是使用block会延长对象的生命周期。delegate 更重一些,需要实现协议,代码更为分散。

一般情况应该优先使用 block。而在这些情况可以考虑 delegate。

1.回调的方法有多个

比如一个网络类,假如只有成功和失败两种情况,每个方法可以设计成单独 block。但假如存在多个方法,比如有成功、失败、缓存、https 验证,网络进度等等,这种情况下,delegate 就要比 block 要好。

2.使用Block更适用于只会调用一次,临时性的。

比如数组遍历,UIView 动画,这些时候用 block 是只会调用一次,用完就删,不会存储起来。

但是对于一些需要存储,并且会调用多次,就很容易出现循环引用问题,比如按钮的点击事件,tableView的heightForRowAtIIndexPath 和cellForRowAtIndexPath需要多次回调。

43.使用block时需要注意的东西

1.当一个对象直接或间接地持有block,而Block内又强引用了该对象,那么这时候就需要使用weak弱应用来解决循环运用的问题,而在block内部使用该弱应用变量的时候如果需要多次调用,需要放置提前被释放是使用__strong 

2.如果需要在Block内部去修改外部的变量,这个时候该变量在block内部只是一个只读拷贝,这个时候需要使用__block来修饰变量。

44.AFNetworking的内部实现原理

整个模块划分为:

网络通信模块(AFURLSessionManager、AFHTTPSessionManger)

网络状态监听模块(Reachability)

网络通信安全策略模块(Security),验证HTTPS请求的证书,模式有三种:

AFSSLPinningModeNone

这个模式表示不做SSL pinning,只跟浏览器一样在系统的信任机构列表里验证服务端返回的证书。若证书是信任机构签发的就会通过,若是自己服务器生成的证书,这里是不会通过的。

AFSSLPinningModeCertificate

这个模式表示用证书绑定方式验证证书,需要客户端保存有服务端的证书拷贝,这里验证分两步,第一步验证证书的域名/有效期等信息,第二步是对比服务端返回的证书跟客户端返回的是否一致。

AFSSLPinningModePublicKey

这个模式同样是用证书绑定方式验证,客户端要有服务端的证书拷贝,只是验证时只验证证书里的公钥,不验证证书的有效期等信息。

网络通信信息序列化/反序列化模块(Serialization):

AFURLRequestSerialization 用于帮助构建NSURLRequest,主要做了两个事情:

让参数可以是NSDictionary, NSArray, NSSet这些类型,再由内部解析成字符串后赋给NSURLRequest

AFURLResponseSerialization负责解析网络返回数据,将返回的数据可能是json字符串,或者xml数据)解析成对象返回。

图片解压

当我们调用UIImage的方法imageWithData:方法把数据转成UIImage对象后,其实这时UIImage对象还没准备好需要渲染到屏幕的数据,现在的网络图像PNG和JPG都是压缩格式,需要把它们解压后才能渲染到屏幕上,这个解压操作是比较耗时的,所以会在子线程处理。

对于iOS UIKit库的扩展(UIKit),主要使用category对UI控件扩展动作(UIButton,UIImage添加通过网络加载图片并且显示出来的方法)。

第一:两套单独的缓存机制

1.AFImagecache : 继承于NSCache,用于图片内存缓存。UIApplicationDidReceiveMemoryWarningNotification 通知用于当内存底时区清除缓存。

2.NSURLCache,系统默认缓存机制,对请求返回的数据进行缓

第三:使用更为简洁的block来作为数据的回调

45.如何设计图片缓存

第一点 缓存的流程:

当我们需要去加载一张照片,首先

根据提供的URL的MD5从内存中查找,如果内存中查找不到

通过NSInvocationOpertion在子线程中从本地沙盒中查找;

如果找到,就将图片缓存到内存中,并显示,否则

从网络加载之后,在子线程保存到本地文件,同时缓存到内存中,并显示。

第二点:通过添加UIImageView(WebCache) UIButton(WebCache)类目来扩展系统类的方法。

第三点 对缓存的控制:使用两个属性分别是 缓存的最大时间(用于判断缓存是否过期) 最大缓存空间。注册对应的监听事件,包括应用退出后台或者结束,这时候清除过去缓存;第二注册存警告通知,这时候现将过期缓存清除,如果还是不够,就从缓存事件的最早到最迟起释放缓存。

第四点:对本地的缓存是通过文件操作类,将图片转化成NSData后写入到本地沙盒中。而内存缓存通过NSCache实现

46.从设计模式的角度分析Delegate、Notification、KVO的区别

delegate的优点:

1.delegate 往往需要和协议共同使用,通过协议可以对需要回调的方法有清晰的定义。

2.一个类可以声明多个协议,保证了灵活性。

缺点:使用起来比较重,需要对应编写协议。

notification 优点:

1.对于消息发布者和接受者并不需要对方的存在,只需要去完成各自的逻辑处理,是一种低藕设计模式,而且接受者的数量并没有限制

2.实现简单,只需要通过notificationCenter来进行消息发送

缺点:

1.发送消息后接收方并没有反馈

2.代码难以跟踪,不好调试。

使用场景:对于具有一定跨度,没有明确指定的观察者,而且观察者数量动态可变的通知方式,如系统事件的通知,不同模块之间类的通信等。

47.单例会有什么弊端

1.对于单例对象,往往是负责比较单一的功能,难以扩展,而且如果在不同情况下需要有不同的状态,单例往往您已保存各自的状态很容易出错。

2.在整个运行过程中一直占有内存。

48.调试 BAD_ACCESS

1.Zombie:zombie的原理是用生成僵尸对象来替换dealloc的实现,当对象引用计数为0的时候,将需要dealloc的对象转化为僵尸对象。如果之后再给这个僵尸对象发消息,则抛出异常,并打印出相应的信息,调试者可以很轻松的找到异常发生位置

2. Xcode 7 后 AddressSanitizer

3.设置全局断点快速定位问题代码所在行

49.触摸事件响应链

当在屏幕上点击的时候,系统会将触摸事件打包成一个UIEvent对象,并放在UIApplication的事件队列,然后 UIApplication 会在队列中将事件传递给 UIWindow,UIWindow会调用内容视图上的hitTest withEvent方法,这个方法会调用视图层级结构上每一个视图的pointInside方法来判断触目点是否在视图内,这样通过从最顶层视图到最底层视图的遍历,找出能够响应触摸事件的视图,而这个时候最后一个可以响应该事件的视图也就是事件处理的最优视图。

事件的响应就从最优视图开始从下网上传递,通过调用继承自UIResponseder的touchs方法,如果调用 [super touchBeign]方法则会继续往上传递,否则再往上传递响应事件。如果UIApplication也不能处理该事件,则将其丢弃。

下面三种情况不能响应事件:

1.隐藏 2.透明度 < 0.01 3.userInteractionEnable = false;

如果有一个不规则图形控件,需要判断能够响应点击事件,需要重写pointInside方法。

50.修改类私有属性,私有成员变量的方法

1.通过KVC setValue forKey

2.runtime class_copyIvarList \ ivar_getName \ object_setIvar

51.类方法load和initialize的区别

区别:Apple的文档很清楚地说明了initialize和load的区别在于:load是类文件被引用就会被触发,而initialize是在类或者其子类的第一次被调用前触发(如创建类的示例对象,调用类方法)。

类别中的定义 :load 全都执行,但先执行类中的方法再执行类别中的方法 ; iniitialize 只执行类别中的

相同点:

在不考虑开发者主动使用的情况下,系统最多会调用一次

如果父类和子类都被调用,父类的调用一定在子类之前

都是为了应用运行提前创建合适的运行环境

使用:

autorelease poo alload方法通常用来进行Method Swizzle

initialize方法一般用于初始化全局变量或静态变量。

52.@autoreleasepool使用场景如下:

1.循环里面包含了大量临时创建的对象。(获取手机照片对相册的block遍历时内部就是用到autoreleasepool。就会产生比较多开销较大的临时对象,使用block来遍历,在遍历的时候会在内部创建自动释放池来对对象及时释放)

2.长时间在后台运行的任务。(如afnetworking)

上一篇下一篇

猜你喜欢

热点阅读