iOS之規(guī)范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_setIvarLayout或class_setWeakIvarLayout來處理strongweak引用。所以不能向存在的類中添加實(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