鍍金池/ 教程/ iOS/ 調(diào)試:案例學(xué)習(xí)
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開發(fā)
Collection View 動(dòng)畫
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫
常見的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場(chǎng)
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場(chǎng)
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

調(diào)試:案例學(xué)習(xí)

沒人寫的代碼是完美無暇的,但調(diào)試代碼我們卻都應(yīng)該有能力能做好。相比提供一個(gè)關(guān)于本話題的隨機(jī)小建議,我更傾向于選擇帶你親身經(jīng)歷一個(gè) bug 修復(fù)的過程,這是一個(gè) UIKit 的 bug,我會(huì)展示我用來理解,隔離,并最終解決這個(gè)問題的流程。

問題

我收到了一個(gè) bug 反饋報(bào)告,當(dāng)快速點(diǎn)擊一個(gè)按鈕來彈出一個(gè) popover 并 dismiss 它的同時(shí),視圖控制器也會(huì)被 dismiss。謝天謝地,還附上了一個(gè)截圖示意,所以第一步 -- 重現(xiàn) bug -- 已經(jīng)被做到了:

http://wiki.jikexueyuan.com/project/objc/images/19-1.gif" alt="" />

我的第一個(gè)猜測(cè)是,我們可能包含了 dismiss 視圖控制器的代碼,我們錯(cuò)誤地 dismiss 了父視圖控制器。然而,當(dāng)使用 Xcode 集成的視圖調(diào)試功能時(shí),很明顯有一個(gè)全局 UIDimmingView 作為 first responder 來響應(yīng)點(diǎn)擊事件:

http://wiki.jikexueyuan.com/project/objc/images/19-2.png" alt="" />

蘋果在 Xcode 6 中添加了調(diào)試視圖層次結(jié)構(gòu)的功能,這一舉動(dòng)很可能是受到非常受歡迎的應(yīng)用 RevealSpark Inspector 的啟發(fā)。相對(duì)于 Xcode,它們?cè)谠S多方面表現(xiàn)更好,功能更多。

使用 LLDB

在可視化調(diào)試出現(xiàn)之前,最常見的做法是在 LLDB 使用 po [[UIWindow keyWindow] recursiveDescription] 來檢查層次結(jié)構(gòu)。它可以以文本形式打印出完整的視圖層次結(jié)構(gòu)

類似于檢查視圖層次,我們也可以用 po [[[UIWindow keyWindow] rootViewController] _printHierarchy] 來檢查視圖控制器。這是一個(gè)蘋果默默在 iOS 8 中為 UIViewController 添加的私有輔助方法 。

(lldb) po [[[UIWindow keyWindow] rootViewController] _printHierarchy]
<PSPDFNavigationController 0x7d025000>, state: disappeared, view: <UILayoutContainerView 0x7b3218d0> not in the window
   | <PSCatalogViewController 0x7b3100d0>, state: disappeared, view: <UITableView 0x7c878800> not in the window
   + <UINavigationController 0x8012c5d0>, state: appeared, view: <UILayoutContainerView 0x8012b7a0>, presented with: <_UIFullscreenPresentationController 0x80116c00>
   |    | <PSPDFViewController 0x7d05ae00>, state: appeared, view: <PSPDFViewControllerView 0x80129640>
   |    |    | <PSPDFContinuousScrollViewController 0x7defa8e0>, state: appeared, view: <UIView 0x7def1ce0>
   |    + <PSPDFNavigationController 0x7d21a800>, state: appeared, view: <UILayoutContainerView 0x8017b490>, presented with: <UIPopoverPresentationController 0x7f598c60>
   |    |    | <PSPDFContainerViewController 0x8017ac40>, state: appeared, view: <UIView 0x7f5a1380>
   |    |    |    | <PSPDFStampViewController 0x8016b6e0>, state: appeared, view: <UIView 0x7f3dbb90>

LLDB 非常強(qiáng)大并且可以腳本化。 Facebook 發(fā)布了一組名為 Chisel 的 Python 腳本集合 為日常調(diào)試提供了非常多的幫助。pviewspvc 等價(jià)于視圖和視圖控制器的層次打印。Chisel 的視圖控制器樹和上面方法打印的很類似,但是同時(shí)還顯示了視圖的尺寸。 我通常用它來檢查響應(yīng)鏈,雖然你可以對(duì)你感興趣的對(duì)象手動(dòng)循環(huán)執(zhí)行 nextResponder,或者添加一個(gè)類別輔助方法,但輸入 presponder object 依舊是迄今為止最快的方法。

添加斷點(diǎn)

我們首先要找出實(shí)際 dismiss 我們視圖控制器的代碼。最容易想到的是在 viewWillDisappear: 設(shè)置一個(gè)斷點(diǎn)來進(jìn)行調(diào)用棧跟蹤:

(lldb) bt
* thread #1: tid = 0x1039b3, 0x004fab75 PSPDFCatalog`-[PSPDFViewController viewWillDisappear:](self=0x7f354400, _cmd=0x03b817bf, animated='\x01') + 85 at PSPDFViewController.m:359, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x004fab75 PSPDFCatalog`-[PSPDFViewController viewWillDisappear:](self=0x7f354400, _cmd=0x03b817bf, animated='\x01') + 85 at PSPDFViewController.m:359
    frame #1: 0x033ac782 UIKit`-[UIViewController _setViewAppearState:isAnimating:] + 706
    frame #2: 0x033acdf4 UIKit`-[UIViewController __viewWillDisappear:] + 106
    frame #3: 0x033d9a62 UIKit`-[UINavigationController viewWillDisappear:] + 115
    frame #4: 0x033ac782 UIKit`-[UIViewController _setViewAppearState:isAnimating:] + 706
    frame #5: 0x033acdf4 UIKit`-[UIViewController __viewWillDisappear:] + 106
    frame #6: 0x033c46a1 UIKit`-[UIViewController(UIContainerViewControllerProtectedMethods) beginAppearanceTransition:animated:] + 200
    frame #7: 0x03380ad8 UIKit`__56-[UIPresentationController runTransitionForCurrentState]_block_invoke + 594
    frame #8: 0x033b47ab UIKit`__40+[UIViewController _scheduleTransition:]_block_invoke + 18
    frame #9: 0x0327a0ce UIKit`___afterCACommitHandler_block_invoke + 15
    frame #10: 0x0327a079 UIKit`_applyBlockToCFArrayCopiedToStack + 415
    frame #11: 0x03279e8e UIKit`_afterCACommitHandler + 545
    frame #12: 0x060669de CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 30
    frame #20: 0x032508b6 UIKit`UIApplicationMain + 1526
    frame #21: 0x000a119d PSPDFCatalog`main(argc=1, argv=0xbffcd65c) + 141 at main.m:15
(lldb) 

利用 LLDB 的 bt 命令,你可以打印斷點(diǎn)。bt all 可以達(dá)到一樣的效果,區(qū)別在于會(huì)打印全部線程的狀態(tài),而不僅是當(dāng)前的線程。

看看這個(gè)棧,我們注意到視圖控制器已經(jīng)被 dismiss 途中,因?yàn)檫@個(gè)方法是在預(yù)定的動(dòng)畫中被調(diào)用的,所以我們需要在更早的地方增加斷點(diǎn)。在這個(gè)例子中,我們關(guān)注的是對(duì)于 -[UIViewController dismissViewControllerAnimated:completion:] 的調(diào)用。我們?cè)?Xcode 的斷點(diǎn)列表中添加一個(gè)符號(hào)斷點(diǎn),并且重新執(zhí)行示例代碼。

Xcode 的斷點(diǎn)接口非常強(qiáng)大,它允許你添加條件,跳過計(jì)數(shù),或者自定義動(dòng)作,比如添加音效和自動(dòng)繼續(xù)等。雖然它們可以節(jié)省相當(dāng)多的時(shí)間,但在這里我們不需要這些特性:

(lldb) bt
* thread #1: tid = 0x1039b3, 0x033bb685 UIKit`-[UIViewController dismissViewControllerAnimated:completion:], queue = 'com.apple.main-thread', stop reason = breakpoint 7.1
  * frame #0: 0x033bb685 UIKit`-[UIViewController dismissViewControllerAnimated:completion:]
    frame #1: 0x03a7da2c UIKit`-[UIPopoverPresentationController dimmingViewWasTapped:] + 244
    frame #2: 0x036153ed UIKit`-[UIDimmingView handleSingleTap:] + 118
    frame #3: 0x03691287 UIKit`_UIGestureRecognizerSendActions + 327
    frame #4: 0x0368fb04 UIKit`-[UIGestureRecognizer _updateGestureWithEvent:buttonEvent:] + 561
    frame #5: 0x03691b4d UIKit`-[UIGestureRecognizer _delayedUpdateGesture] + 60
    frame #6: 0x036954ca UIKit`___UIGestureRecognizerUpdate_block_invoke661 + 57
    frame #7: 0x0369538d UIKit`_UIGestureRecognizerRemoveObjectsFromArrayAndApplyBlocks + 317
    frame #8: 0x03689296 UIKit`_UIGestureRecognizerUpdate + 3720
    frame #9: 0x032a226b UIKit`-[UIWindow _sendGesturesForEvent:] + 1356
    frame #10: 0x032a30cf UIKit`-[UIWindow sendEvent:] + 769
    frame #21: 0x032508b6 UIKit`UIApplicationMain + 1526
    frame #22: 0x000a119d PSPDFCatalog`main(argc=1, argv=0xbffcd65c) + 141 at main.m:15

如我們所說!正如預(yù)期的,全屏 UIDimmingView 接收到我們的觸摸并且在 handleSingleTap: 中處理,接著轉(zhuǎn)發(fā)到 UIPopoverPresentationController 中的 dimmingViewWasTapped: 方法來 dismiss 視圖控制器 (就像它該做的那樣),然而。當(dāng)我們快速點(diǎn)擊時(shí),這個(gè)斷點(diǎn)被調(diào)用了兩次。這里有第二個(gè) dimming 視圖?還是說調(diào)用的是相同的實(shí)例?我們只有斷點(diǎn)時(shí)候的程序集,所以調(diào)用 po self 是無效的。

調(diào)用約定入門

根據(jù)程序集和函數(shù)調(diào)用約定的一些基本知識(shí),我們依然可以拿到 self 的值。iOS ABI Function Call Guide 和在 iOS 模擬器時(shí)使用的 Mac OS X ABI Function Call Guide 都是極好的資源。

我們知道每個(gè) Objective-C 方法都有兩個(gè)隱式參數(shù):self_cmd。于是我們所需要的就是在棧上的第一個(gè)對(duì)象。在 32-bit 架構(gòu)中,棧信息保存在 $esp 里,所以在 Objective-C 方法中你可以你可以使用 po *(int*)($esp+4) 來獲取 self,以及使用 p (SEL)*(int*)($esp+8) 來獲取 _cmd。$esp 里的第一個(gè)值是返回地址。隨后的變量保存在 $esp+12$esp+16 以及依此類推的其他位置上。

x86-64 架構(gòu) (那些包含 arm64 芯片 iPhone 設(shè)備的模擬器) 提供了更多寄存器,所以變量放置在 $rdi,$rsi$rdx,$rcx,$r8$r9 中。所有后續(xù)的變量在 $rbp 棧上。開始于 $rbp+16$rbp+24 等。

armv7 架構(gòu)的變量通常放置在 $r0$r1,$r2,$r3 中,接著移動(dòng)到 $sp 棧上:

(lldb) po $r0
<PSPDFViewController: 0x15a1ca00 document:<PSPDFDocument 0x15616e70 UID:amazondynamososp2007_0c7fb1fc6c0841562b090b94f0c1c890 files:1 pageCount:16 isValid:1> page:0>

(lldb) p (SEL)$r1
(SEL) $1 = "dismissViewControllerAnimated:completion:"

arm64 類似于 armv7,然而,因?yàn)橛懈嗟募拇嫫?,?$x0$x7 的整個(gè)范圍都用來存放變量,之后回到棧寄存器 $sp 中。

你可以學(xué)到更多關(guān)于 x86x86-64 的棧布局知識(shí),還可以閱讀 AMD64 ABI Draft 來進(jìn)行深入。

使用 Runtime

跟蹤方法執(zhí)行的另一種做法是重寫方法,并在調(diào)用父類之前加入日志輸出。然而,手動(dòng) swizzling 調(diào)試起來雖然方便,但是在要花的時(shí)間上來說其實(shí)效率不高。在前一陣子,我寫了一個(gè)很小的叫做 Aspects 的庫,來專門做這件事情。它可以用于生產(chǎn)代碼,但是我大部分時(shí)候只用它來調(diào)試和寫測(cè)試用例。(如果你對(duì) Aspects 感興趣,你可以在這里了解更多相關(guān)知識(shí)。)

#import "Aspects.h"

[UIPopoverPresentationController aspect_hookSelector:NSSelectorFromString(@"dimmingViewWasTapped:") 
                                         withOptions:0 
                                          usingBlock:^(id <AspectInfo> info, UIView *tappedView) {
    NSLog(@"%@ dimmingViewWasTapped:%@", info.instance, tappedView);
} error:NULL];

這里我們?yōu)?dimmingViewWasTapped: 添加了一個(gè)鉤子,它是私有方法 — 因此我們使用 NSSelectorFromString。你可以驗(yàn)證方法是否存在,并通過使用 iOS Runtime Headers 來查找?guī)缀趺總€(gè)框架類的其他私有和公共方法。這個(gè)項(xiàng)目利用了不可能在運(yùn)行時(shí)真正地隱藏方法這一事實(shí),它在所有類中查找方法并,從而創(chuàng)建了一個(gè)比蘋果所提供給我們的相比,更完整的頭文件。(當(dāng)然,調(diào)用私有 API 并不是一個(gè)好主意 — 這里只是用來便于理解到底發(fā)生了什么)

在鉤子方法的日志中,我們獲得如下輸出:

PSPDFCatalog[84049:1079574] <UIPopoverPresentationController: 0x7fd09f91c530> dimmingViewWasTapped:<UIDimmingView: 0x7fd09f92f800; frame = (0 0; 768 1024)>
PSPDFCatalog[84049:1079574] <UIPopoverPresentationController: 0x7fd09f91c530> dimmingViewWasTapped:<UIDimmingView: 0x7fd09f92f800; frame = (0 0; 768 1024)>

我們看到對(duì)象地址完全相同,所以我們可憐的 dimming 視圖真的被調(diào)用了兩次,我們可以使用 Aspects 來查看具體 dismiss 方法調(diào)用在了哪個(gè)控制器上:

[UIViewController aspect_hookSelector:@selector(dismissViewControllerAnimated:completion:)
                          withOptions:0
                           usingBlock:^(id <AspectInfo> info) {
    NSLog(@"%@ dismissed.", info.instance);
} error:NULL];
2014-11-22 19:24:51.900 PSPDFCatalog[84210:1084883] <UINavigationController: 0x7fd673789da0> dismissed.
2014-11-22 19:24:52.209 PSPDFCatalog[84210:1084883] <UINavigationController: 0x7fd673789da0> dismissed.

兩次 dimming 視圖都調(diào)用了主導(dǎo)航控制器的 dismiss 方法。如果子視圖控制器存在的話,視圖控制器的 dismissViewControllerAnimated:completion: 會(huì)將視圖控制器的 dismiss 請(qǐng)求轉(zhuǎn)發(fā)到它的子視圖控制器中,否則它將 dismiss 自己。所以第一次 dismiss 請(qǐng)求執(zhí)行于 popover,而第二次,導(dǎo)航控制器本身被 dismiss 了。

查找臨時(shí)方案

現(xiàn)在我們知道發(fā)生了什么事情 — 接下來我們可以進(jìn)入為何發(fā)生的環(huán)節(jié)。UIKit 是閉源代碼,但是我們使用像 Hopper 這樣的反匯編工具來解讀 UIKit 程序集并且仔細(xì)看看 UIPopoverPresentationController 里發(fā)生了什么事情。你可以在 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/UIKit.framework 里找到二進(jìn)制文件。然后在 Hopper 里使用 File -> Read Executable to Disassemble...,這將遍歷整個(gè)二進(jìn)制文件并且將代碼符號(hào)化。32-bit 反匯編是最成熟的一個(gè)。所以你選擇 32-bit 文件可以拿到最好的結(jié)果。Hex-Rays 出品的 IDA 是另一個(gè)很強(qiáng)大很昂貴的反匯編程序,通??梢蕴峁?a rel="nofollow" >更好的結(jié)果:

http://wiki.jikexueyuan.com/project/objc/images/19-3.png" alt="" />

一些匯編語言的基礎(chǔ)知識(shí)對(duì)閱讀代碼會(huì)非常有用。不過,你也可以使用偽代碼視圖來得到類似于 C 代碼的結(jié)果:

http://wiki.jikexueyuan.com/project/objc/images/19-4.png" alt="" />

閱讀偽代碼結(jié)果讓人大開眼界。這里有兩個(gè)代碼路徑 — 其中一個(gè)是如果 delegate 實(shí)現(xiàn)了 popoverPresentationControllerShouldDismissPopover: 時(shí)調(diào)用,另一個(gè)在沒有實(shí)現(xiàn)時(shí)調(diào)用 — 兩個(gè)代碼路徑實(shí)際上相當(dāng)不同。delegate 實(shí)現(xiàn)了委托方法的那個(gè)路徑中,包含了 if (controller.presented && !controller.dismissing),而另一個(gè)代碼路徑 (我們現(xiàn)在實(shí)際進(jìn)入的) 卻沒有,并總是調(diào)用 dismiss。通過內(nèi)部信息,我們可以嘗試通過實(shí)現(xiàn)我們自己的 UIPopoverPresentationControllerDelegate 來繞開這個(gè) bug:

- (BOOL)popoverPresentationControllerShouldDismissPopover:(UIPopoverPresentationController *)popoverPresentationController {
    return YES;
}

我的第一次嘗試是把創(chuàng)建 popover 的主視圖控制器設(shè)為 delegate。然而它破壞了 UIPopoverController。雖然文檔沒提,但 popover 控制器會(huì)在 _setupPresentationController 中將自己設(shè)為 delegate,另外,移除這個(gè) delegate 將造成破壞。之后,我使用了一個(gè) UIPopoverController 的子類并直接添加了上面的方法。這兩個(gè)類之間的聯(lián)系并沒有文檔化,而且我們的解決方案依賴于這個(gè)沒有文檔的行為;不過,這個(gè)實(shí)現(xiàn)是匹配默認(rèn)行為的,它純粹是為了解決這個(gè)問題,所以它是經(jīng)得起未來考驗(yàn)的代碼。

反饋 Radar

現(xiàn)在請(qǐng)不要停下。我們通常需要為這樣的繞開問題的方案寫一些文檔,但還有一件重要的事情是,給 Apple 提交一個(gè) radar。這么做會(huì)帶來額外的好處,這能讓你驗(yàn)證你是否真正理解這個(gè) bug,并且在你的程序中沒有其他副作用 — 如果你之后放棄支持這個(gè) iOS 版本,你可以很容易回滾代碼并測(cè)試這個(gè) radar 是否修正過。

// UIPopoverController 是它的 contentViewController,即 UIPopoverPresentationController 的默認(rèn)的 delegate
//
// 這里有一個(gè) bug:當(dāng)雙擊 diming 視圖時(shí),presentation 視圖控制器將調(diào)用兩次
// dismissViewControllerAnimated:completion:,并 dismiss 掉它的父控制器.
//
// 通過實(shí)現(xiàn)這個(gè) delegate 可以讓代碼運(yùn)行另一條正確地檢查了是否正在 dismiss 的代碼路徑
// rdar://problem/19067761
- (BOOL)popoverPresentationControllerShouldDismissPopover:(UIPopoverPresentationController *)popoverPresentationController {
    return YES;
}

寫一個(gè) Radar 實(shí)際上是非常有趣的挑戰(zhàn),它并不像你想象的那么花時(shí)間。用一個(gè)示例,你將幫助那些勞累蘋果工程師,沒有示例,工程師將很有可能推遲,甚至不考慮這個(gè) radar。我為這個(gè)問題創(chuàng)建了一個(gè)大約 50 行代碼的例子,還包括一些意見和解決方案。單視圖的模板通常是創(chuàng)建一個(gè)示例的最快方式。

現(xiàn)在,我們都知道蘋果的 Radar 網(wǎng)頁并沒有那么好用,不過你可以不使用它。QuickRadar 是一個(gè)用來提交 radar 的非常優(yōu)秀的 Mac 前端,同時(shí)它會(huì)自動(dòng)提交一個(gè)副本到 OpenRadar。此外,復(fù)制 radar 也極其方便。你應(yīng)該馬上下載它,另外,如果你覺得例子里這樣的錯(cuò)誤值得被修復(fù),可以復(fù)制 rdar://19067761。

并不是所有問題都可以用一些簡單的方案繞開,但這些步驟將幫助你找到更好的解決問題的方法,或者至少幫助你的理解為什么某些事情會(huì)發(fā)生。

參考