iOS之规范3
能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
答:
-
不能向编译后得到的类中增加实例变量
因为编译后的类已经注册在
runtime
中,类结构体中的objc_ivar_list
实例变量的链表 和instance_size
实例变量的内存大小已经确定,同时runtime
会调用class_setIvarLayout
或class_setWeakIvarLayout
来处理strong
weak
引用。所以不能向存在的类中添加实例变量; -
能向运行时创建的类中添加实例变量
运行时创建的类是可以添加实例变量,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。
runloop和线程有什么关系?
runloop:一直在运行着的循环.
-
关系:
run loop和线程是紧密相连的,可以这样说run loop是为了线程而生,没有线程,它就没有存在的必要。Run loops是线程的基础架构部分, Cocoa 和 CoreFundation 都提供了 run loop 对象方便配置和管理线程的 run loop (以下都以 Cocoa 为例)
-
主线程的run loop默认是启动的.
APPDelegate中的UIApplicationMain()函数,这个方法会为main thread设置一个NSRunLoop对象。
* 对其它线程来说,run loop默认是没有启动的。如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。
* 在任何一个 Cocoa 程序的线程中,都可以通过以下代码来获取到当前线程的 run loop:NSRunLoop *runloop = [NSRunLoop currentRunLoop];
-
参考链接:《Objective-C之run loop详解》。
runloop的 mode
mode 作用:
答案:主要用来指定事件在运行循环(runloop)中的优先级的。
- NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态------<mark>apple 公开提供
- UITrackingRunLoopMode:ScrollView滑动时
- UIInitializationRunLoopMode:启动时
- NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合------<mark>apple 公开提供
以+ scheduledTimerWithTimeInterval...的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决?
答案:
-
原因:
RunLoop只能运行在一种mode下,如果要换mode,当前的runloop也需要停下重启成新的。
+ scheduledTimerWithTimeInterval是将 timer 以 defaultmode 添加至当前 runloop(主线程)中。
利用这个机制,ScrollView滚动过程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode会切换到UITrackingRunLoopMode来保证ScrollView的流畅滑动:只能在NSDefaultRunLoopMode模式下处理的事件会影响scrllView的滑动(相互影响)。
-
解决方案:
将timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)来解决。(即在任何 mode 下都执行 timer 的计时操作)
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
```
objc 使用什么机制管理对象内存?
答案:
- 通过 retaincount 机制来决定对象是否需要释放。
- 每次 runloop 时,都会检测是否有 retaincount 为0的对象,若有,则释放之。
ARC 通过什么方式帮助管理内存?
答案:
- ARC 之于 MRC,不是简单的在编译时添加
retain、release、autorelease
,而是在编译时
和运行时
2个部分共同管理内存。 - 编译时:ARC 使用底层 c 接口实现
retain、release、autorelease
,此举使之性能更好。成对优化。 - 运行时:
一个 autorelease 对象在什么时刻释放?
(如在一个 ctrl 的 viewDidLoad 中创建)
答:
分为2种情况:
-
手动干预释放时机
指定autoreleasepool 就是所谓的:当前作用域大括号结束时释放.
-
系统自动释放
不手动指定autoreleasepool.
Autorelease对象是在当前的runloop迭代结束时释放的。
而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop.继而Autorelease对象出了作用域之后,会被添加到最近一次创建的自动释放池中,并会在当前的 runloop 迭代结束时释放.
@autoreleasepool 当自动释放池被销毁或者耗尽时,会向自动释放池中的所有对象发送 release 消息,释放自动释放池中的所有对象。
苹果是如何实现autoreleasepool的?
答案:
autoreleasepool 以一个队列数组
形式实现,主要以三个函数完成:
- objc_autoreleasepoolPush
- objc_autoreleasepoolPop
- objc_autorelease
BAD_ACCESS在什么情况下出现?
答:访问野指针。如:对一个已经释放的对象发消息、访问该对象的成员,死循环等。
使用block时什么情况会发生引用循环,如何解决?
答:
对象中强引用
了 block,在 block 中又强引用
了该对象。即会发生引用循环。
解决方案:
将对象用__block
、__weak
修饰之后,再在 block 中使用。
使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?
答:
所谓引用循环
是指双向的强引用,所以那些单向的强引用
(block 只强引用 self )没有问题。
-
单项引用不考虑的情况:
[UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = xyz; }];
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * notification) {
self.someProperty = xyz; }];
* 双项引用考虑的情况:
> 使用一些参数中可能含有 ivar 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用.
```
__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
} );
```
```
__weak __typeof__(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
__typeof__(self) strongSelf = weakSelf;
[strongSelf dismissModalViewControllerAnimated:YES];
}];
//self --> _observer --> block --> self 显然这也是一个循环引用。
```
## dispatch_barrier_async的作用是什么?
答:
dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行。
> 在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用 barrier 来等待之前任务完成,避免数据竞争等问题。 dispatch_barrier_async 函数会等待追加到Concurrent Dispatch Queue并行队列中的操作全部执行完之后,然后再执行 dispatch_barrier_async 函数追加的处理,等 dispatch_barrier_async 追加的处理执行结束之后,Concurrent Dispatch Queue才恢复之前的动作继续执行。
> 注意:使用 dispatch_barrier_async ,该函数只能搭配自定义并行队列 dispatch_queue_t 使用。不能使用: dispatch_get_global_queue ,否则 dispatch_barrier_async 的作用会和 dispatch_async 的作用一模一样。 )
dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"dispatch_async1");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:4];
NSLog(@"dispatch_async2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch_barrier_async");
[NSThread sleepForTimeInterval:4];
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async3");
});
结果:
2012-09-25 16:20:33.967 gcdTest[45547:11203] dispatch_async1
2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_async2
2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_barrier_async
2012-09-25 16:20:40.970 gcdTest[45547:11303] dispatch_async3
## 如何手动出发一个 value 的 KVO?
答案:
* 触发 KVO 原理:
> 键值观察通知依赖于 NSObject 的两个方法: `willChangeValueForKey:` 和 `didChangevlueForKey:` 。
>
> 在一个被观察属性发生改变之前, `willChangeValueForKey:` 一定会被调用,这就 会记录旧的值。而当改变发生后, didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
>
> 如果可以手动实现这些调用,就可以实现“手动触发”了。
* 手动出发目的:希望能控制`回调的调用时机`
```
- (void)viewDidLoad
{
[super viewDidLoad];
[self willChangeValueForKey:@"now"]; // “手动触发self.now的KVO”,必写。
[self didChangeValueForKey:@"now"]; // “手动触发self.now的KVO”,必写。
}
```
* 不建议`手动触发`
* KVO 的 keyPath不仅支持属性,也支持实例变量。
## apple 实现 KVO 的方式
KVO 的实现依赖于 isa-swizzling 技术。
* 当对象的一个属性被注册监听后,对象的 isa 指针转而指向一个中间类,而非原来的类对象了。
* 中间类对象:继承自原来的类对象。但重写了被观察的属性 setter 方法。
* 重写 setter 方法:在原来 setter 方法之前之后(willChangeValueForKey、didChangevlueForKey),通知所有观察对象:值的改变。
* 继而会调用observeValueForKey:ofObject:change:context:。
## EXC_BAD_ACCESS之BUG解决
1. 重写 objc 的 respondsToselector 方法,显示出现`EXEC_BAD_ACCESS`前访问的最后一个object:
```
#ifdef _FOR_DEBUG_
-(BOOL) respondsToSelector:(SEL)aSelector {
printf("SELECTOR: %s\n", [NSStringFromSelector(aSelector) UTF8String]);
return [super respondsToSelector:aSelector];
}
#endif
```
2. 通过设置 [NSZombieEnable](http://mobile.51cto.com/iphone-279455.htm)
3. 设置全局断点快速定位问题代码所在行
4. xcode7中有 address Sanitizer