iOS之规范3

iOS之规范3

参考链接:参考1,参考2,参考3

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

答:

  • 不能向编译后得到的类中增加实例变量

    因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表 和 instance_size 实例变量的内存大小已经确定,同时 runtime 会调用 class_setIvarLayoutclass_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):默认,空闲状态------apple 公开提供
  • UITrackingRunLoopMode:ScrollView滑动时
  • UIInitializationRunLoopMode:启动时
  • NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合------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

你可能感兴趣的:(iOS之规范3)