iOS 項(xiàng)目總結(jié)

一、前言

盡管早就想開寫這么一個(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 也有新的解決辦法:

image

如圖所示點(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/"

image

然后在項(xiàng)目中,就能使用 FIXME:&TODO: 來讓 Xcode 顯示提醒,當(dāng)然你可以自定義標(biāo)識(shí)符。

image

這段腳本代碼筆者本來就是從網(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ì)有什么問題。往下看吧:

image

往控制器中拖入一個(gè) UIScrollView ,上下左右約束都為 Safe Area。
就像筆者第一次使用一樣,直接放一個(gè) UIViewUIScrollView 中,并約束為上下左右都等于父視圖的邊界。

image

問題就來了,報(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。

image

然后在內(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ì)于一些超出屏幕的界面,很方便。

image

2.4 內(nèi)存泄漏

最近導(dǎo)入了微信的一個(gè)三方內(nèi)存泄漏檢測(cè)工具。

MLeaksFinder

發(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)
}

這里的 vcself 是一回事。

  • 不會(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 方法,坑爹!

目前采用的在 viewWillAppearviewWillDisappear 方法中分別添加和移除。

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ù), 大家需要資料的加小編群哦!

?著作權(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)容

  • 群山連綿不絕,就如同棋盤上凸起的棋子一樣,在這片廣袤而又略帶野性的原始村落,準(zhǔn)確來說應(yīng)該是在人類歷史上不斷進(jìn)化,隨...
    曾彧閱讀 465評(píng)論 0 0
  • 還記得你十幾歲時(shí)暗戀的那個(gè)男孩子嗎? 上課時(shí)有心無意碰到一起的目光,會(huì)躲閃的不像樣。別人不理解,可你就是喜歡...
    矯揉造作老阿姨閱讀 296評(píng)論 0 1
  • 今天上班,早飯后跟大寶約好上午完成作業(yè),下午玩,晚上回來做手工。誰知趁我上班不在家,玩伴們都找了家里,三個(gè)人又撐起...
    明懿媽媽閱讀 250評(píng)論 0 3

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