iOS之規(guī)范3

iOS之規(guī)范3

參考鏈接:參考1參考2,參考3

能否向編譯后得到的類中增加實(shí)例變量?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量?為什么?

答:

  • 不能向編譯后得到的類中增加實(shí)例變量

    因?yàn)榫幾g后的類已經(jīng)注冊(cè)在 runtime 中,類結(jié)構(gòu)體中的 objc_ivar_list 實(shí)例變量的鏈表 和 instance_size 實(shí)例變量的內(nèi)存大小已經(jīng)確定,同時(shí) runtime 會(huì)調(diào)用 class_setIvarLayoutclass_setWeakIvarLayout 來處理 strong weak 引用。所以不能向存在的類中添加實(shí)例變量;

  • 能向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量

    運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量,調(diào)用 class_addIvar 函數(shù)。但是得在調(diào)用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。

runloop和線程有什么關(guān)系?

  • runloop:一直在運(yùn)行著的循環(huán).

  • 關(guān)系:

    run loop和線程是緊密相連的,可以這樣說run loop是為了線程而生,沒有線程,它就沒有存在的必要。Run loops是線程的基礎(chǔ)架構(gòu)部分, Cocoa 和 CoreFundation 都提供了 run loop 對(duì)象方便配置和管理線程的 run loop (以下都以 Cocoa 為例)

    • 主線程的run loop默認(rèn)是啟動(dòng)的.

      APPDelegate中的UIApplicationMain()函數(shù),這個(gè)方法會(huì)為main thread設(shè)置一個(gè)NSRunLoop對(duì)象。
      * 對(duì)其它線程來說,run loop默認(rèn)是沒有啟動(dòng)的。

      如果你需要更多的線程交互則可以手動(dòng)配置和啟動(dòng),如果線程只是去執(zhí)行一個(gè)長(zhǎng)時(shí)間的已確定的任務(wù)則不需要。
      * 在任何一個(gè) Cocoa 程序的線程中,都可以通過以下代碼來獲取到當(dāng)前線程的 run loop:

      NSRunLoop *runloop = [NSRunLoop currentRunLoop];

參考鏈接:《Objective-C之run loop詳解》。

runloop的 mode

mode 作用:

答案:主要用來指定事件在運(yùn)行循環(huán)(runloop)中的優(yōu)先級(jí)的。

  • NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默認(rèn),空閑狀態(tài)------<mark>apple 公開提供
  • UITrackingRunLoopMode:ScrollView滑動(dòng)時(shí)
  • UIInitializationRunLoopMode:?jiǎn)?dòng)時(shí)
  • NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合------<mark>apple 公開提供

以+ scheduledTimerWithTimeInterval...的方式觸發(fā)的timer,在滑動(dòng)頁面上的列表時(shí),timer會(huì)暫定回調(diào),為什么?如何解決?

答案:

  • 原因:

    RunLoop只能運(yùn)行在一種mode下,如果要換mode,當(dāng)前的runloop也需要停下重啟成新的。

    + scheduledTimerWithTimeInterval是將 timer 以 defaultmode 添加至當(dāng)前 runloop(主線程)中。

    利用這個(gè)機(jī)制,ScrollView滾動(dòng)過程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode會(huì)切換到UITrackingRunLoopMode來保證ScrollView的流暢滑動(dòng):只能在NSDefaultRunLoopMode模式下處理的事件會(huì)影響scrllView的滑動(dòng)(相互影響)。

  • 解決方案:

    將timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)來解決。(即在任何 mode 下都執(zhí)行 timer 的計(jì)時(shí)操作)

    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
     target:self
     selector:@selector(timerTick:)
     userInfo:nil
     repeats:YES];
    

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
```

objc 使用什么機(jī)制管理對(duì)象內(nèi)存?

答案:

  • 通過 retaincount 機(jī)制來決定對(duì)象是否需要釋放。
  • 每次 runloop 時(shí),都會(huì)檢測(cè)是否有 retaincount 為0的對(duì)象,若有,則釋放之。

ARC 通過什么方式幫助管理內(nèi)存?

答案:

  • ARC 之于 MRC,不是簡(jiǎn)單的在編譯時(shí)添加retain、release、autorelease,而是在編譯時(shí)運(yùn)行時(shí)2個(gè)部分共同管理內(nèi)存。
  • 編譯時(shí):ARC 使用底層 c 接口實(shí)現(xiàn)retain、release、autorelease,此舉使之性能更好。成對(duì)優(yōu)化。
  • 運(yùn)行時(shí):

一個(gè) autorelease 對(duì)象在什么時(shí)刻釋放?

(如在一個(gè) ctrl 的 viewDidLoad 中創(chuàng)建)

答:

分為2種情況:

  • 手動(dòng)干預(yù)釋放時(shí)機(jī)

    指定autoreleasepool 就是所謂的:當(dāng)前作用域大括號(hào)結(jié)束時(shí)釋放.

  • 系統(tǒng)自動(dòng)釋放

    不手動(dòng)指定autoreleasepool.

    Autorelease對(duì)象是在當(dāng)前的runloop迭代結(jié)束時(shí)釋放的。

    而它能夠釋放的原因是系統(tǒng)在每個(gè)runloop迭代中都加入了自動(dòng)釋放池Push和Pop.繼而Autorelease對(duì)象出了作用域之后,會(huì)被添加到最近一次創(chuàng)建的自動(dòng)釋放池中,并會(huì)在當(dāng)前的 runloop 迭代結(jié)束時(shí)釋放.

    @autoreleasepool 當(dāng)自動(dòng)釋放池被銷毀或者耗盡時(shí),會(huì)向自動(dòng)釋放池中的所有對(duì)象發(fā)送 release 消息,釋放自動(dòng)釋放池中的所有對(duì)象。

蘋果是如何實(shí)現(xiàn)autoreleasepool的?

答案:
autoreleasepool 以一個(gè)隊(duì)列數(shù)組形式實(shí)現(xiàn),主要以三個(gè)函數(shù)完成:

  • objc_autoreleasepoolPush
  • objc_autoreleasepoolPop
  • objc_autorelease

BAD_ACCESS在什么情況下出現(xiàn)?

答:訪問野指針。如:對(duì)一個(gè)已經(jīng)釋放的對(duì)象發(fā)消息、訪問該對(duì)象的成員,死循環(huán)等。

使用block時(shí)什么情況會(huì)發(fā)生引用循環(huán),如何解決?

答:

對(duì)象中強(qiáng)引用了 block,在 block 中又強(qiáng)引用了該對(duì)象。即會(huì)發(fā)生引用循環(huán)。

解決方案:

將對(duì)象用__block、__weak修飾之后,再在 block 中使用。

使用系統(tǒng)的某些block api(如UIView的block版本寫動(dòng)畫時(shí)),是否也考慮引用循環(huán)問題?

答:

所謂引用循環(huán)是指雙向的強(qiáng)引用,所以那些單向的強(qiáng)引用(block 只強(qiáng)引用 self )沒有問題。

  • 單項(xiàng)引用不考慮的情況:

[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; }];


* 雙項(xiàng)引用考慮的情況:

    > 使用一些參數(shù)中可能含有 ivar 的系統(tǒng) api ,如 GCD 、NSNotificationCenter就要小心一點(diǎn):比如GCD 內(nèi)部如果引用了 self,而且 GCD 的其他參數(shù)是 ivar,則要考慮到循環(huán)引用.
    
    ```
    __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 顯然這也是一個(gè)循環(huán)引用。
    ```
    
## dispatch_barrier_async的作用是什么?

答:

dispatch_barrier_async是在前面的任務(wù)執(zhí)行結(jié)束后它才執(zhí)行,而且它后面的任務(wù)等它執(zhí)行完成之后才會(huì)執(zhí)行。

> 在并行隊(duì)列中,為了保持某些任務(wù)的順序,需要等待一些任務(wù)完成后才能繼續(xù)進(jìn)行,使用 barrier 來等待之前任務(wù)完成,避免數(shù)據(jù)競(jìng)爭(zhēng)等問題。 dispatch_barrier_async 函數(shù)會(huì)等待追加到Concurrent Dispatch Queue并行隊(duì)列中的操作全部執(zhí)行完之后,然后再執(zhí)行 dispatch_barrier_async 函數(shù)追加的處理,等 dispatch_barrier_async 追加的處理執(zhí)行結(jié)束之后,Concurrent Dispatch Queue才恢復(fù)之前的動(dòng)作繼續(xù)執(zhí)行。

> 注意:使用 dispatch_barrier_async ,該函數(shù)只能搭配自定義并行隊(duì)列 dispatch_queue_t 使用。不能使用: dispatch_get_global_queue ,否則 dispatch_barrier_async 的作用會(huì)和 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");
});


結(jié)果:

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


## 如何手動(dòng)出發(fā)一個(gè) value 的 KVO?

答案:

* 觸發(fā) KVO 原理:

    > 鍵值觀察通知依賴于 NSObject 的兩個(gè)方法: `willChangeValueForKey:` 和 `didChangevlueForKey:` 。
    > 
    > 在一個(gè)被觀察屬性發(fā)生改變之前, `willChangeValueForKey:` 一定會(huì)被調(diào)用,這就 會(huì)記錄舊的值。而當(dāng)改變發(fā)生后, didChangeValueForKey: 會(huì)被調(diào)用,繼而 observeValueForKey:ofObject:change:context: 也會(huì)被調(diào)用。
    > 
    > 如果可以手動(dòng)實(shí)現(xiàn)這些調(diào)用,就可以實(shí)現(xiàn)“手動(dòng)觸發(fā)”了。
    
* 手動(dòng)出發(fā)目的:希望能控制`回調(diào)的調(diào)用時(shí)機(jī)`

    ```
    - (void)viewDidLoad
{
    [super viewDidLoad];
    [self willChangeValueForKey:@"now"]; // “手動(dòng)觸發(fā)self.now的KVO”,必寫。
    [self didChangeValueForKey:@"now"]; // “手動(dòng)觸發(fā)self.now的KVO”,必寫。
}

    ```
    
* 不建議`手動(dòng)觸發(fā)`
* KVO 的 keyPath不僅支持屬性,也支持實(shí)例變量。

## apple 實(shí)現(xiàn) KVO 的方式

KVO 的實(shí)現(xiàn)依賴于 isa-swizzling 技術(shù)。

*  當(dāng)對(duì)象的一個(gè)屬性被注冊(cè)監(jiān)聽后,對(duì)象的 isa 指針轉(zhuǎn)而指向一個(gè)中間類,而非原來的類對(duì)象了。
 
* 中間類對(duì)象:繼承自原來的類對(duì)象。但重寫了被觀察的屬性 setter 方法。

* 重寫 setter 方法:在原來 setter 方法之前之后(willChangeValueForKey、didChangevlueForKey),通知所有觀察對(duì)象:值的改變。
* 繼而會(huì)調(diào)用observeValueForKey:ofObject:change:context:。


## EXC_BAD_ACCESS之BUG解決

1. 重寫 objc 的 respondsToselector 方法,顯示出現(xiàn)`EXEC_BAD_ACCESS`前訪問的最后一個(gè)object:

    ```
     #ifdef _FOR_DEBUG_  
-(BOOL) respondsToSelector:(SEL)aSelector {  
    printf("SELECTOR: %s\n", [NSStringFromSelector(aSelector) UTF8String]);  
    return [super respondsToSelector:aSelector];  
}  
#endif  
    ```
    
2. 通過設(shè)置 [NSZombieEnable](http://mobile.51cto.com/iphone-279455.htm)
3. 設(shè)置全局?jǐn)帱c(diǎn)快速定位問題代碼所在行
4. xcode7中有 address Sanitizer
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容