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