一、前言
盡管早就想開寫這么一個(gè)系列,但遲遲沒有付諸實(shí)踐。就現(xiàn)在開始吧,這個(gè)系列會(huì)記錄我項(xiàng)目中遇到的問題和一些我以前不知道的小知識(shí)點(diǎn)等等。
二、問題
2.1 消除 Pod 文件中的警告
作為強(qiáng)迫癥患者,看到一個(gè)警告就會(huì)讓我不舒服。但以前不知道 Pod 文件中的警告可以被忽略,直到有一天發(fā)現(xiàn)警告全部不見了,原來是同事在 Podfile 文件中加入了這么一個(gè)配置:
inhibit_all_warnings!
2.2 引入 R.swift 三方后,自定義 Warning 顯示,腳本沖突
以前寫 OC 時(shí),我們可以用如下代碼來讓 Xcode 給出 Warning 提示:
#warning
但在 Swift 項(xiàng)目代碼中,這種寫法無效了,當(dāng)然 Swift 也有新的解決辦法:
如圖所示點(diǎn)擊 New Run Script Phase, 然后在添加的 Run Script 中寫入如下腳本代碼:
TAGS="TODO:|FIXME:"
echo "searching ${SRCROOT} for ${TAGS}"
find "${SRCROOT}" \( -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/"
然后在項(xiàng)目中,就能使用 FIXME:&TODO: 來讓 Xcode 顯示提醒,當(dāng)然你可以自定義標(biāo)識(shí)符。
這段腳本代碼筆者本來就是從網(wǎng)上復(fù)制的,也從來沒仔細(xì)看過,直到引入了 R.swift(強(qiáng)烈推薦) 這個(gè)三方,造成沖突了。沖突的原因很簡(jiǎn)單,因?yàn)樯厦婺_本代碼中匹配的是 .swift,結(jié)果這個(gè)三方名字中就含有這個(gè),尼瑪!所以解決方法就是忽略 Pod 文件。
TAGS="TODO:|FIXME:"
echo "searching ${SRCROOT}/項(xiàng)目工程文件本名 for ${TAGS}"
find "${SRCROOT}/項(xiàng)目工程文件名" \( -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/"
2.3 直接在 StoryBoard 中使用 UIScrollView 布局界面
最近筆者完全淪為 IB 黨了,哈哈!
沒有嘗試過的讀者會(huì)認(rèn)為這個(gè)不會(huì)有什么問題。往下看吧:
往控制器中拖入一個(gè) UIScrollView ,上下左右約束都為 Safe Area。
就像筆者第一次使用一樣,直接放一個(gè) UIView 到 UIScrollView 中,并約束為上下左右都等于父視圖的邊界。
問題就來了,報(bào)錯(cuò)說 UIScrollView 需要新的位置或大小約束,筆者目前也沒想通為啥會(huì)報(bào)錯(cuò)。
實(shí)際開發(fā)中,頁面的最終的內(nèi)容肯定是確定的,所以可以根據(jù)界面反推 UIScrollView 的布局。但是就像上圖中那樣,如果一直給你顯示布局錯(cuò)誤,筆者和部分讀者會(huì)覺得很煩。解決方法很簡(jiǎn)單:
直接添加內(nèi)容視圖的寬高約束等于 UIScrollView,然后將高度約束的優(yōu)先級(jí)調(diào)整為小于 1000。
然后在內(nèi)容視圖上布局界面時(shí)根據(jù)子視圖將內(nèi)容視圖高度確定就OK了,此時(shí)因?yàn)榍懊嬖O(shè)置的高度約束優(yōu)先級(jí)小于 1000 ,所以會(huì)優(yōu)先使用子視圖確定的高度。當(dāng)然讀者也可以在布局完成時(shí)將前面的高度約束刪除掉。
還有就是可以將控制器的界面設(shè)置為 Freeform,對(duì)于一些超出屏幕的界面,很方便。
2.4 內(nèi)存泄漏
最近導(dǎo)入了微信的一個(gè)三方內(nèi)存泄漏檢測(cè)工具。
發(fā)現(xiàn)同事的代碼中有很多的內(nèi)存泄漏,經(jīng)過一番排查,這里也小小總結(jié)一下常見的和遇到的不常見的內(nèi)存泄漏情況。
2.4.1 常見的
- 閉包循環(huán)引用
// 用來設(shè)置導(dǎo)航欄返回按鈕的一個(gè)方法
self.setUseCommonNavigationBackButtonWithHandler { (_) in
self.navigationController?.popViewController(animated: true)
}
絕對(duì)多數(shù)讀者肯定會(huì)避免這種問題,但我發(fā)現(xiàn)同事寫了一個(gè)變種:
let vc = UIViewController()
vc.setUseCommonNavigationBackButtonWithHandler { (_) in
vc.navigationController?.popViewController(animated: true)
}
這里的 vc 和 self 是一回事。
- 不會(huì)釋放對(duì)象持有需要釋放對(duì)象
場(chǎng)景: 單例持有外部需要釋放的對(duì)象
筆者項(xiàng)目中使用的是 SVProgressHUD 這個(gè)三方來做的加載,其中有一個(gè)方法是用來設(shè)置加載控件的容器視圖的:
+ (void)setContainerView:(nullable UIView*)containerView;
項(xiàng)目中同事將當(dāng)前控制器的視圖作為了容器視圖進(jìn)行了配置,然后 SVProgressHUD 單例對(duì)象對(duì)其進(jìn)行了持有,當(dāng)控制器 pop 時(shí),這個(gè)視圖因?yàn)楸粏卫钟校瑹o法釋放。
- 對(duì)象循環(huán)持有
場(chǎng)景:子控制器持有父控制器
對(duì)于一些需要向控制器中添加子控制器的界面,點(diǎn)擊子控制器的內(nèi)容,然后進(jìn)行 push操作,但因?yàn)檫@個(gè)場(chǎng)景真正需要進(jìn)行 push 操作的其實(shí)是父控制器,所以往往都會(huì)將父控制器傳給子控制器。
如果一不小心,聲明了一個(gè)屬性強(qiáng)持有了父控制器,就造成了循環(huán)持有。正確的應(yīng)該是弱持有父控制器。
- 監(jiān)聽和通知需要移除
這個(gè)沒什么說的,項(xiàng)目中沒發(fā)現(xiàn),只是提一下
2.4.2 不常見的
這里的不常見定義為筆者以前不知道的
- WKUserContentController移除位置
場(chǎng)景:WKWebView 進(jìn)行 JS 交互
最初是在 deinit 方法中進(jìn)行的移除操作,經(jīng)過測(cè)試發(fā)現(xiàn),WKUserContentController 實(shí)例對(duì)象進(jìn)行了添加后,不會(huì)走 deinit 方法,坑爹!
目前采用的在 viewWillAppear 和 viewWillDisappear 方法中分別添加和移除。
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
userContent.add(self, name: "xxx")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
userContent.removeScriptMessageHandler(forName: "xxx")
}
-
UIAlertController內(nèi)存泄漏
直接模擬場(chǎng)景代碼
class DetailViewController: UIViewController {
var alertController: CustomViewController!
override func viewDidLoad() {
super.viewDidLoad()
alertController = CustomViewController(title: nil, message: nil, preferredStyle: UIAlertControllerStyle.actionSheet)
let textAction = UIAlertAction(title: "拍照", style: UIAlertActionStyle.default) { (action:UIAlertAction) in
// 問題所在處
self.title = "Test"
}
alertController.addAction(textAction)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
present(alertController, animated: true, completion: nil)
}
deinit {
print("DetailViewController init")
}
}
DetailViewController 持有了 alertController
alertController 添加 textAction 后持有了它
textAction 事件閉包中使用了 self, 于是持有了 DetailViewController
一個(gè)循環(huán)引用環(huán)形成,GG。
解決方法也很簡(jiǎn)單,閉包中使用 weak。
2.5 swift協(xié)議默認(rèn)實(shí)現(xiàn)的靜態(tài)分發(fā)
詳見忽略子類中的協(xié)議默認(rèn)實(shí)現(xiàn)不參與動(dòng)態(tài)分派
這里我項(xiàng)目中場(chǎng)景是定義了一個(gè)協(xié)議來控制當(dāng)前控制器是否能夠滑動(dòng)返回
protocol NavigationControllerGestureAble: NSObjectProtocol {
var isCanSlidePop: Bool { get }
}
extension UIViewController: NavigationControllerGestureAble {
// 這里@objc需要注意
@objc var isCanSlidePop: Bool {
return true
}
}
...其余的略
這里默認(rèn)為 ture,因?yàn)?OC 中沒有計(jì)算屬性,其實(shí)就是一個(gè) set&get 方法的語法糖。所以這里需要加 @objc(我是這樣理解的),于是我們?cè)谛枰P(guān)閉的控制器中重寫這個(gè)并返回 false 就好了。
作為一個(gè)開發(fā)者,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要這是一個(gè)我的iOS交流群:597268708,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經(jīng)驗(yàn),討論技術(shù), 大家需要資料的加小編群哦!