每天梳理1~2個(gè)問題,堅(jiān)持一下(三)

翻新翻新,新開一篇
以后應(yīng)該大部分都用swift來寫,不寫手生要廢了。。。

19.06.28

swift中的@objc

補(bǔ)一下上周五的。。。周五去浪了。。。
我們的ERP老古董項(xiàng)目的swift部分還停留在swift3.0,拿一臺(tái)mac更新了Xcode之后發(fā)現(xiàn),好家伙,swift最低支持版本是4.0了,這就意味著swift部分早晚就要有個(gè)大變動(dòng)了。
之前老覺得銀行這類應(yīng)用做的爛體驗(yàn)差還不改,現(xiàn)在也能體諒那種穩(wěn)定為主能用就好的心態(tài)了。
說一下3.0和4.0比較大的差異之一吧,@objc的出現(xiàn)

swift雖然想通過編譯的時(shí)候就盡可能地確定能確定的事情來提高性能,但其實(shí)還是離不開很多動(dòng)態(tài)化運(yùn)行時(shí)的事情,就離不開我們的老大哥OC。4.0之前在語法的要求上沒有界定那么清楚,編譯器也可能自動(dòng)幫我做了一些事情,但是4.0開始,基于OC運(yùn)行時(shí)的地方就要前綴上@objc,指明這里需要OC的特性去處理。

比較常用的一些地方:
1.代理中方法的可選類型
2.selector選擇器選擇的方法
3.觀察者觀察的某一個(gè)屬性
...

只要是需要OC特性的都需要在其前面添加前綴@objc,當(dāng)然你忘了也沒關(guān)系,編譯器會(huì)給報(bào)錯(cuò)修正提示你

這里有個(gè)地方要注意,就是加了@objc修飾之后,這個(gè)類型可能就不是一個(gè)完完全全的swift類型了。

舉個(gè)例子:

@objc protocol caiXunKunSportProtocol : class {
    func playBall(ballType:NSInteger)
    func run()
    @objc optional func canChoose()
}

extension caiXunKunSportProtocol {
    func playBall(ballType:NSInteger) {
        if ballType == 1 {
            print("籃球")
        }
    }
}

@objc對(duì)協(xié)議的影響,會(huì)使得swift中協(xié)議的特性失效,上面extension中擴(kuò)展了協(xié)議的默認(rèn)實(shí)現(xiàn),服從此協(xié)議的類可以不實(shí)現(xiàn)這個(gè)方法因?yàn)橛心J(rèn)實(shí)現(xiàn),但caiXunKunSportProtocol被@objc修飾之后,這個(gè)特性消失,擴(kuò)展的協(xié)議方法默認(rèn)實(shí)現(xiàn)失效,服從此協(xié)議的類必須實(shí)現(xiàn)這個(gè)協(xié)議方法。

19.07.01

把輪播圖的demo整理了下

帶間隔的輪播圖demo

寫一點(diǎn)造輪子的想法,當(dāng)然都是很小的控件輪子,目前的能力造不出大級(jí)別的輪子:
個(gè)人還是很傾向于造輪子的,雖然說造輪子耗時(shí)耗力,但是確實(shí)很鍛煉個(gè)人的思維和能力。日常工作的時(shí)候很少有時(shí)間去造輪子,為了減少時(shí)間,大部分是幾天前就開始考慮這個(gè)輪子的大體方案,如果要做的時(shí)候連方案都沒有就暫時(shí)放棄,先用第三方的,后續(xù)參考第三方做出思路方案,如果能想到方案,就開始做一些初步的可行性分析,如果可行就開始自己實(shí)施。

19.07.02

關(guān)于collectionView和tableView的選取

因?yàn)橹皩戇^一次瀑布流的效果,深知自定義layout的工作量有多大,布局完完全全地就是自己在計(jì)算在設(shè)置,所以從那次之后,能用tableview的地方就盡可能地用tableview,能用flowlayout的地方就盡量用flowlayout,萬不得已不會(huì)自己坑自己去自定義layout。

上面說的有點(diǎn)夸張了,回到選取的方案上來,由于collectionView足夠強(qiáng)大,tableview能實(shí)現(xiàn)的collectionView也能實(shí)現(xiàn),但工作量可能提升了不少。那么如何對(duì)這兩者進(jìn)行一個(gè)合適的選取呢?我覺得主要考慮一下幾個(gè)方面:

1.重用范圍的覆蓋。這里是最重要的,考慮好重用范圍的覆蓋有利于效率和以后的擴(kuò)展。舉個(gè)例子,如果一個(gè)頁面中由很多大小不同的item組成的分區(qū)構(gòu)成,如果每個(gè)item是可重用的(都是同一種或者很有限的種類),同時(shí)它又是規(guī)整的高度(不是瀑布流的形式),像下面這樣:

section1:
item item item
item item2
section2:
item item
item item2 item

那么建議使用collectionView,因?yàn)榇藭r(shí)最小的重用單元就是每個(gè)item,對(duì)于每個(gè)item我們也是可以控制其左右間距和itemSize的大小。你可能會(huì)說這種明顯就是collectionView最簡(jiǎn)單了,首選肯定是collectionView,那么下面這種我相信很多人可能會(huì)選擇tableView:

section1:
item item item
section2:
item item item

這種規(guī)整劃一的結(jié)構(gòu)tableView實(shí)現(xiàn)起來也是很簡(jiǎn)單的,這個(gè)時(shí)候你的選擇就非常地重要了,tableView能做,以每行3個(gè)item進(jìn)行重用,但是后期擴(kuò)展如果item稍微一變化,就需要多個(gè)cell的種類,對(duì)于擴(kuò)展性是非常糟糕的,如果改動(dòng)過多,很可能你的tableView完全力不從心。

所以第一點(diǎn)非常要強(qiáng)調(diào)的就是:重用覆蓋的范圍,理論上來說,重用覆蓋的范圍越大即重用視圖劃分越細(xì)致,那么對(duì)于后期的擴(kuò)展性就越好。

2.復(fù)雜度。說復(fù)雜度就要說layout,自定義layout令人頭大,尤其是奇怪的規(guī)則多樣的layout,所以從個(gè)人角度來說,我可能會(huì)盡量避免自定義layout。向下面這樣:

item item item item
item item item
item XXX XXX
item******* XXX

XXX表示縱向繼續(xù)向下延伸,***表示橫向繼續(xù)延伸。

對(duì)于這種我的思路是這樣的:如果能抽出某塊重用的視圖,就用tableView,后期擴(kuò)展先不考慮,如果不能抽出,普通的flowLayout顯然不能滿足,需要自定義layout,此時(shí)請(qǐng)注意,這個(gè)排列規(guī)則一定要讓產(chǎn)品寫清楚,這個(gè)規(guī)則是你自定義layout的基礎(chǔ),拿到這個(gè)規(guī)則不要忙著開工,多想想規(guī)則是否有缺陷是否需要改進(jìn)或者優(yōu)化,不然中途做一半了再又變動(dòng)真的是頭疼*2。
按理說這里應(yīng)該是用自定義layout的,但由于自定義layout復(fù)雜度不一,而且坑也不少,所以如果難度太大超過自己可控范圍不建議自定義layout。

3.tableView行不通的,只能用collectionView的。一般來說這種被限制會(huì)出現(xiàn)在多列且高度不統(tǒng)一上,比如瀑布流的效果,這樣一來如果是tableView就找不到完全可以重用的模塊,只能去使用collectionView,也就只能老老實(shí)實(shí)去自定義layout了。

19.07.03

gpx文件配置好像對(duì)一些APP失效了

這兩天在用模擬定位的時(shí)候發(fā)現(xiàn)一些APP不能被模擬了,不知道是限制了什么還在找原因。

gpx文件可以讓我們配置一些經(jīng)緯度用在測(cè)試時(shí)候的定位模擬:

1.創(chuàng)建一個(gè)gpx文件,cmd+n,選擇下面這個(gè)文件:


GPX文件

2.在新生成的gpx文件中輸入我們需要的經(jīng)緯度,由于你使用不同的的拾取坐標(biāo)系統(tǒng)有差異,所以轉(zhuǎn)換后的也可能會(huì)有點(diǎn)差異。

配置好之后將其進(jìn)行關(guān)聯(lián),點(diǎn)擊Edit Scheme,Options選項(xiàng)卡下面:


關(guān)聯(lián)配置

運(yùn)行起來就可以用了,這個(gè)模擬是全局效果,理論上所有的APP都是可以被模擬到的,但最近不知道怎么了,有些APP似乎加強(qiáng)了位置的校驗(yàn),導(dǎo)致不能被模擬。目前還能被模擬的有高德、京東、美團(tuán)外賣等等。有空測(cè)試下不能被模擬的原因,初步猜測(cè)可能是wifi或者sim卡移動(dòng)信號(hào)再次做了位置校驗(yàn)。

19.07.04

ReactiveCocoa要解決的問題

才開始了解ReactiveCocoa,目前的工程中也只是局部用了ReactiveCocoa,沒有大范圍地使用。網(wǎng)上很多關(guān)于ReactiveCocoa的文章只是簡(jiǎn)單地告訴你怎么簡(jiǎn)單地使用,并沒有一個(gè)系統(tǒng)地講解。去搜集比較核心的問題的時(shí)候也是比較困難,總結(jié)一下我自己搜集到的和一些理解。

ReactiveCocoa要解決的問題。

我們的APP對(duì)事務(wù)的處理可以看做是一個(gè)流,我們把各個(gè)要處理的事務(wù)放到這個(gè)流中,A->B->C...依次處理。但總有一些特殊的事務(wù):

1.耗時(shí)的事務(wù)
2.互相依賴的事務(wù)
3.引起流變化的事務(wù)(改變當(dāng)前事務(wù)的同時(shí)會(huì)對(duì)其他事務(wù)產(chǎn)生影響)

這類事務(wù)如果只是單純地放入我們的事務(wù)流中顯然是不可行的,所以我們要特殊處理他們,于是異步任務(wù)就承接了這類事務(wù),異步的出現(xiàn)像是我們的主事務(wù)流中分出了一些小流,這些小流單獨(dú)承接了某個(gè)或多個(gè)事務(wù)的處理,處理完畢之后匯入大流中,這樣一來,我們的主流依舊流暢,不受這類事務(wù)的影響。

到了這里,之前的問題似乎都得到了解決,但引入異步的解決方案便會(huì)帶來異步的問題:如何組織管理這些異步事務(wù)和依賴事務(wù)。

這樣描述可能并不形象,舉一個(gè)形象點(diǎn)的例子:多個(gè)網(wǎng)絡(luò)請(qǐng)求如何等其完全回調(diào)之后進(jìn)行處理?一個(gè)表單填寫完畢之后如何對(duì)填充的數(shù)據(jù)逐個(gè)校驗(yàn)之后再進(jìn)行下一步?如何及時(shí)響應(yīng)某個(gè)多變的事務(wù)?

顯然,目前的狀態(tài)下我們可能要寫不少組織這些事務(wù)的操作。ReactiveCocoa就是為解決這些事情而生。ReactiveCocoa在不影響異步事務(wù)的情況下更好地管理這些事務(wù)流,使每個(gè)事務(wù)流都能很好地“關(guān)聯(lián)”在一起,匯入主事務(wù)流中??梢哉fReactiveCocoa是一個(gè)管理事務(wù)流的框架。在這個(gè)框架下,所有的事務(wù)都能產(chǎn)生信號(hào)和被訂閱信號(hào),這樣一來,不同的事務(wù)的通訊就很簡(jiǎn)單了。你可能會(huì)說,通知也能實(shí)現(xiàn)啊。但是通知的方式使得代碼結(jié)構(gòu)混亂,毫無聚合,在開發(fā)和維護(hù)上非常弱勢(shì)。而ReactiveCocoa響應(yīng)式的框架,能使得代碼高聚合低耦合,統(tǒng)一使用了信號(hào)的方式,使得代碼結(jié)構(gòu)統(tǒng)一且清晰。

19.07.05

如何學(xué)習(xí)一個(gè)新的框架

本來準(zhǔn)備周五寫的,周五還沒把RAC全部概覽一邊,所以等著概覽之后再來記錄更有體會(huì),把我個(gè)人的感受總結(jié)一下。

RAC其實(shí)很早就導(dǎo)入了,也有一小部分代碼在用,但也很局限于幾個(gè)方法中,例如同步網(wǎng)絡(luò)請(qǐng)求、合并多個(gè)信號(hào)為一個(gè)信號(hào)等常用的幾個(gè)用法,沒有去擴(kuò)展也沒有去了解過內(nèi)部的結(jié)構(gòu)。周末的時(shí)候?qū)φ麄€(gè)框架做了一個(gè)大體的概覽,又有了進(jìn)一步的認(rèn)識(shí),正好也把學(xué)習(xí)新框架的感想記錄一下。

我們可以像看書一樣,去學(xué)習(xí)一個(gè)新的事物:

1.了解主題和思想。首先我們應(yīng)該知道,要使用的這個(gè)框架要做什么事情,為了解決什么問題,用了什么方式去做;

2.了解目錄,也就是整個(gè)框架的構(gòu)成??赡芤婚_始并沒有一個(gè)現(xiàn)成的目錄讓我們?nèi)チ私猓覀兛梢运鸭幌戮W(wǎng)上的資料進(jìn)行一個(gè)大體目錄的梳理,這一步也就是對(duì)整個(gè)框架一個(gè)初步的了解,只需要只要有些什么,什么和什么好像有聯(lián)系等等,不用深入去了解。

3.了解重要的章節(jié),也就是框架中比較核心的幾個(gè)部分。比如RAC中,比較重要的有信號(hào)、command、多播等等,這些比較重要的是需要我們特別去深入了解的;

4.遇到不會(huì)的先做記錄,快速推進(jìn)。我們?cè)趯W(xué)一個(gè)新東西的時(shí)候經(jīng)常會(huì)遇到有不會(huì)不懂的地方,如果卡在了一個(gè)地方而短時(shí)間內(nèi)又無法有突破不妨先將其放一邊,繼續(xù)向前推進(jìn);

5.找到聯(lián)系,融會(huì)貫通。如果不是系統(tǒng)性地學(xué)習(xí)一個(gè)事務(wù),我們學(xué)到的很大一部分都是點(diǎn)狀的,學(xué)了知識(shí)點(diǎn)A,再去學(xué)知識(shí)點(diǎn)B、C、D等等。有很多的記憶法,都是基于事物之間的聯(lián)系。這里我們也可以借鑒這種,上面第4點(diǎn)說道快速推進(jìn),是為了能獲得更多地點(diǎn),當(dāng)我們點(diǎn)足夠多,就能盡可能多地理清點(diǎn)和點(diǎn)之間的聯(lián)系,有了聯(lián)系能幫助我們更好地理解這些點(diǎn),有些學(xué)著學(xué)著就自然而通;

6.冥想,梳理學(xué)到的。學(xué)而不思則罔,說的就是要思考。無論是從書本上還是別人的總結(jié)中獲得的知識(shí),都需要你自己有一套自己對(duì)其的認(rèn)識(shí)才算是學(xué)到了,否則只是簡(jiǎn)單地對(duì)其做了一次記憶copy。經(jīng)常思考有助于對(duì)知識(shí)的加深和理解,溫故而知新。

19.07.08

swift和OC中的方法

OC中的方法只認(rèn)方法名和參數(shù)個(gè)數(shù),無視參數(shù)類型,而swift對(duì)于參數(shù)類型也做了限制,如果參數(shù)類型不符合,則不認(rèn)為是同一個(gè)方法。

這個(gè)問題出現(xiàn)在我想在swift用OC的一個(gè)創(chuàng)建cell的寫法中,這個(gè)寫法是這樣的:動(dòng)態(tài)創(chuàng)建cell

每個(gè)cell對(duì)應(yīng)一個(gè)model,在子類cell中重寫父類的賦值方法,利用多態(tài)的特性,方法的參數(shù)由baseModel變換成子類的model。在OC中這樣玩的順風(fēng)順?biāo)?,但是到了swift中,重寫父類方法變得不可行,因?yàn)閰?shù)類型不一樣,swift并不認(rèn)為這是覆蓋了父類的方法,override不可用。

暫時(shí)還沒想好swift中如何去完全替代OC中的繼承+工廠模式,明天想一想

19.07.09

處理昨天的問題

一開始想著如何使用協(xié)議的方式去實(shí)現(xiàn),沒有想出比較好的方案,暫時(shí)先用最簡(jiǎn)單的方式去解決吧,swift中的泛型

測(cè)試文件:one 或者 two 均繼承自base


測(cè)試文件

在基類中我們寫入某個(gè)方法,參數(shù)定為泛型:

    func setUpColor<T>(model:T)  {
        print("基類的方法")
    }

在子類中我們?nèi)ブ貙戇@個(gè)方法,并做對(duì)應(yīng)的實(shí)現(xiàn),例如twoModel和twoView中:

twoModel中有一個(gè)變量名name:

class oneModel: BaseModel {
    var viewColor : UIColor = UIColor.red
}

twoView中重寫基類方法,并指定參數(shù)model為對(duì)應(yīng)類型的model

override func setUpColor<T>(model: T) {
        if let model = model as? twoModel {
            print(model.name)
        }
    }

在外部調(diào)用上,用了一組測(cè)試數(shù)據(jù):

        let classNameArray : Array<String> = ["Play.BaseView","Play.twoView","Play.oneView"]
        let modelNameArray : Array<String> = ["Play.BaseModel","Play.twoModel","Play.oneModel"]
        var viewArray = Array<BaseView>()
        var modelArray = Array<BaseModel>()
        
        for str in classNameArray {
            if let cls = NSClassFromString(str) {
                if let cls = cls as? BaseView.Type {
                    let view = cls.init(frame: CGRect.init(x: 10, y: 10, width: 10, height: 10))
                    viewArray.append(view)
                }
            }
        }
        
        for str in modelNameArray {
            if let cls = NSClassFromString(str) {
                if let cls = cls as? BaseModel.Type {
                    let model = cls.init()
                    modelArray.append(model)
                }
            }
        }
        
        //賦值
        for i in 0...2 {
            viewArray[i].setUpColor(model: modelArray[I])
        }

這樣一來,基本上已經(jīng)和OC版本的一致了。
swift中的泛型T似乎就是為了解決這類問題而生的,通過泛型的使用,使我們能運(yùn)用到多態(tài)的特性,不過在這方面上OC感覺更加“如魚得水”一些。
之前有被大佬說過swift有自己的特性,如果一直拿OC的思想去寫代碼,就算你設(shè)計(jì)模式用的再好,也只是把語言單純地?fù)Q下而已,并沒有發(fā)揮swift真正的優(yōu)勢(shì)。Emmm,準(zhǔn)備抽空好好感受下。。。

19.07.10

swift閉包對(duì)變量的捕獲

swift的閉包和python的閉包幾乎是一個(gè)模子中造出來的,兩者不論是從語法上還是結(jié)構(gòu)上都非常的相近。擴(kuò)大一下閉包的范圍,我個(gè)人覺得block就是閉包的另外一種體現(xiàn),閉包這種形式的構(gòu)造在python中非常常用,例如python中裝飾器上的閉包,在swift中也是比較常用的,但可能是受OC的影響,個(gè)人更喜歡用Block,覺得閉包的寫法更偏向于C的函數(shù)式寫法,所以用的也比較少,今天梳理一下閉包對(duì)于外部臨時(shí)變量的捕獲。

來一個(gè)小的例子:

func FuncBlock() -> ((NSInteger)->(NSInteger)) {
        var num = 0
        func addNumber(x:NSInteger) -> NSInteger {
            num += x
            return num
        }
        return addNumber
    }

這是一個(gè)比較簡(jiǎn)單的閉包,內(nèi)部函數(shù)addNumber捕獲了在FuncBlock中的num這個(gè)臨時(shí)變量。我們進(jìn)行一次打?。?/p>

let fuc = FuncBlock()
print(fuc(1))
print(fuc(2))
print(fuc(3))

1
3
6

我們可以看到臨時(shí)變量num好像延長(zhǎng)了它的生命周期,它并沒有隨著FuncBlock的結(jié)束而結(jié)束。
這是由于內(nèi)部函數(shù)addNumber對(duì)num進(jìn)行了捕獲,捕獲指的是獲取到num的內(nèi)存區(qū)域并進(jìn)行讀寫,在addNumber的生命周期中保證了num的生命周期。如何證明內(nèi)部函數(shù)addNumber對(duì)num是“引用”了原始的地址,而不是值的copy呢?
我們加入地址打印看一下:

func FuncBlock() -> ((NSInteger)->(NSInteger)) {
        var num = 0
        withUnsafeMutablePointer(to: &num, { (ptr: UnsafeMutablePointer<Int>) in
            print(ptr)
        })
        func addNumber(x:NSInteger) -> NSInteger {
            withUnsafeMutablePointer(to: &num, { (ptr: UnsafeMutablePointer<Int>) in
                print(ptr)
            })
            num += x
            return num
        }
        return addNumber
    }

0x0000600001d65030
0x0000600001d65030
1
0x0000600001d65030
3
0x0000600001d65030
6

可以看到從一開始到被捕獲讀寫,num的地址在內(nèi)存中沒有變化。
這說明閉包對(duì)外部變量的捕獲是以引用的方式進(jìn)行的捕獲。

19.07.11

正弦曲線

高中學(xué)的知識(shí),長(zhǎng)期不用早已忘得一干二凈,最近要用到,撿起來梳理一下。

正弦函數(shù)y=Asin(ωx+φ)+k
A :振幅,表示曲線的波動(dòng)高低;
ω :角速度,表示多少為一個(gè)曲線周期或者;
φ: 初相,表示X軸上的偏移;
k : 偏距,表示Y軸上的偏移;

通常我們會(huì)將公式和貝塞爾曲線一起使用,畫出一條正弦的曲線,像下面這樣:

UIBezierPath *bezierPath = [UIBezierPath bezierPath];
for (CGFloat i = 0.0; i < 200; i++) {
        CGFloat point_Y = 10 * sin((M_PI * 2)/200.0 * i) +20;
        [bezierPath addLineToPoint:CGPointMake(i, point_Y)];
    }

如果要使其做出波浪的動(dòng)畫效果,則需要使φ不停地移動(dòng),相當(dāng)于每間隔φ的距離快速重繪一次線條,達(dá)到動(dòng)畫效果,像下面這樣(需要計(jì)時(shí)器不斷調(diào)用displayOffsetX方法):

- (void)setUpFuncSetting {
    //以下三項(xiàng)是可以確定配置的,動(dòng)畫效果由sin_X的變動(dòng)產(chǎn)生
    sin_A = 10;
    sin_W = (M_PI * 2) / self.bounds.size.width;
    sin_C = self.bounds.size.height * 0.5;
    sin_CumulativeData = 0;
}

#pragma mark 處理X軸上的偏移累加,只有不斷累加重新畫Path才有動(dòng)畫效果
- (void)displayOffsetX {
    sin_CumulativeData += sin_W * 0.01 * self.bounds.size.width;
    [self drawPathWithCumulativeData:sin_CumulativeData];
}

#pragma mark 動(dòng)畫效果,繪制路徑waveLayer的路徑
- (void)drawPathWithCumulativeData:(CGFloat)cumulativeData {

    UIBezierPath *bezierPath = [UIBezierPath bezierPath];
    [bezierPath moveToPoint:CGPointMake(0, sin_C)]; //起點(diǎn)
    
    //正弦曲線部分(用float類型使曲線更加細(xì)膩)
    for (CGFloat i = 0.0; i < self.bounds.size.width; i++) {
        CGFloat point_Y = sin_A * sin(sin_W * i + cumulativeData) + sin_C;
        [bezierPath addLineToPoint:CGPointMake(i, point_Y)];
    }
    self.waveLayer.path = bezierPath.CGPath;
}

19.07.12

weakSelf和StrongSelf

找些資料的時(shí)候無意中搜到了這個(gè),也算是當(dāng)時(shí)遺留的一個(gè)問題,順便查閱了一些文檔和別人的見解,自己梳理了一個(gè)思路去理解:為什么block中需要再次使用StrongSelf去引用一次weakSelf。

為了解決循環(huán)引用問題,我們經(jīng)常會(huì)用weak去修飾self,使其引用計(jì)數(shù)不+1,這點(diǎn)理解起來沒有任何問題,但推薦的寫法是在block內(nèi)部再次使用Strong去修飾weakSelf,這點(diǎn)理解起來有點(diǎn)奇怪,我外部已經(jīng)使用了weak做了弱引用處理,內(nèi)部再用Strong不是會(huì)引起循環(huán)引用嗎?

我的理解是:Strong和weak在編譯之后的狀態(tài)是不同的,weakSelf會(huì)被放在Block中成為Block結(jié)構(gòu)體的一部分,被Block所持有,而StrongSelf則只是一個(gè)普通的內(nèi)部作用域的變量,并沒有成為Block結(jié)構(gòu)體的一部分,但使指向?qū)ο笠糜?jì)數(shù)+1,造成了循環(huán)引用,避免提前釋放產(chǎn)生不可預(yù)期的一些后果,保證回調(diào)執(zhí)行完畢。這點(diǎn)是非常重要的,當(dāng)Blcok作用域結(jié)束,這個(gè)變量的生命周期也就結(jié)束了,相當(dāng)于強(qiáng)制將其置為nil,此時(shí)循環(huán)引用打破。

If ‘weakSelf’ is equal to ‘self’, then ‘strongSelf’ retains it, and it stays retained until the block returns, when its released. It’s all or nothing.

參考文章:文章

19.07.15

解決非weak形式下的循環(huán)引用

這類一般是加入到runLoop中的定時(shí)器、通知、WK或者UIWeb與JS的交互引起的。

1.針對(duì)控制器類型的,善用viewWillAppear和viewWillDisappear這一對(duì)組合。例如WK中的ScriptMessageHandler,我們?cè)趘iewWillAppear中添加addScriptMessageHandlerForName,在viewWillDisappear中removeScriptMessageHandlerForName。一些文章在解決這類循環(huán)引用中喜歡引入一個(gè)中間類,我覺得是非常沒有必要的。

2.針對(duì)視圖類型的,使用willMoveToSuperview解決。一些視圖控件中往往加入了NSTimer、CADisplaylink或者通知等會(huì)造成循環(huán)引用的方案。對(duì)于視圖來說,比較好的方式就是在willMoveToSuperview這個(gè)方法中處理循環(huán)引用的問題,舉個(gè)解決CADisplaylink的例子:

#pragma mark 父視圖狀態(tài)變化,此處方式內(nèi)存泄露
- (void)willMoveToSuperview:(UIView *)newSuperview {
    [super willMoveToSuperview:newSuperview];
    
    //還存在父視圖則將計(jì)時(shí)器放入loop
    if (newSuperview) {
        [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    } else {
        //不存在則移除loop,同時(shí)將其置位失效和nil
        [self.displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
        [self.displayLink invalidate];
        self.displayLink = nil;
    }
}

這里通過判斷其父視圖是否為nil來處理持有的CADisplaylink屬性。

19.07.16

沒有被指針引用生成的對(duì)象會(huì)被立即釋放

一直以來我都以為OC中對(duì)象被釋放的時(shí)機(jī)都是在loop的一次循環(huán)迭代后,直到下面的一種情況出現(xiàn):


對(duì)象釋放時(shí)機(jī)測(cè)試

在A_Model和B_Model的dealloc中分別打印了A被銷毀了和B被銷毀了。按照之前的認(rèn)知分析,我覺得這里生成的對(duì)象釋放的時(shí)機(jī)都會(huì)在當(dāng)前的loop循環(huán)迭代之后,結(jié)果卻不一樣:


測(cè)試對(duì)象被銷毀的順序

可以看到,有指針指向的對(duì)象釋放時(shí)機(jī)符合猜想的邏輯,但是沒有指針指向的對(duì)象卻立即釋放掉了,這和之前想的有些不一樣,之前認(rèn)為對(duì)象生成之后都會(huì)放到loop循環(huán)中等待被釋放的時(shí)機(jī),現(xiàn)在看來沒有被用到的對(duì)象是會(huì)被立即釋放的。怎么去解釋這個(gè)地方呢?我個(gè)人理解是被指針?biāo)傅膶?duì)象,指針的作用于是在當(dāng)前的方法中,首先會(huì)保證在方法結(jié)束之前這個(gè)對(duì)象是有效不會(huì)被釋放的,方法結(jié)束跳出作用域之后,這個(gè)時(shí)候?qū)ο缶捅环诺絣oop中等待合適的時(shí)機(jī)被釋放;如果沒有指針?biāo)赶蛞矝]有任何其他對(duì)象對(duì)其引用計(jì)數(shù)加一,那么這個(gè)對(duì)象就是創(chuàng)建即銷毀的狀態(tài),它不需要再被加入到loop的循環(huán)中,立刻執(zhí)行了釋放。(可能不準(zhǔn)確歡迎指正)。

19.07.17

被指針指向的對(duì)象釋放的順序

以下僅討論有被指針指向的對(duì)象。
還是上面的例子,我們可以看到A對(duì)象比B對(duì)象釋放要晚,這是偶然還是必然呢?這里是必然的。

autoreleasepool是由若干個(gè)autoreleasepoolpage以鏈表的形式組成的,而autoreleasepoolpage的結(jié)構(gòu)是棧。在每個(gè)loop的迭代開始,系統(tǒng)自動(dòng)插入autoreleasepoolpush,開始將這次循環(huán)迭代的事物加入到page中,A對(duì)象先生成則先加入,B對(duì)象后生成則后加入。在循環(huán)迭代結(jié)束后插入autoreleasepoolpop,一個(gè)loop的循環(huán)迭代就生成了。我們知道棧是先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu),當(dāng)進(jìn)行釋放的時(shí)候,從pop開始往上依次進(jìn)行,顯然是后加入的B先進(jìn)行釋放,而A則在B之后進(jìn)行釋放。

19.07.18

分類沒被import,屬性或者方法不能被調(diào)用

今天又碰到這個(gè)討論了,寫了分類之后沒有import是否能直接使用(OC),明顯是不能的,我們做個(gè)簡(jiǎn)單的測(cè)試也是可以測(cè)試出來的。OC中對(duì)文件的引入必須要手動(dòng)import一次,單純的創(chuàng)建文件是無法被引入的,LLVM的前端直接就可以反映出來(沒有聯(lián)想),編譯期也會(huì)報(bào)未找到的錯(cuò)誤,分類也是同樣的。

swift中是可以的,swift中的文件是不需要import的,默認(rèn)全局都可以引用到。

19.07.19

談?wù)勲x職和面試

今天簽完字拿到離職證明就徹底走完了離職程序,算是結(jié)束了在成都的首個(gè)工作。

上周一提的離職,離職原因的話是多方面的吧,周一當(dāng)天也沒有什么因素刺激我,就是覺得不舒服想離職了,這個(gè)時(shí)候還沒找工作,裸辭吧,提了申請(qǐng),定到今天完成離職流程。

雖然說自己目前沒有負(fù)債,不過裸辭也要頂著不少的壓力,不能當(dāng)個(gè)閑人啊,所以還是要找下工作。

我一直覺得如果想要更好的一方面是努力,而一方面是敢于舍棄當(dāng)下安穩(wěn)的現(xiàn)狀。后者隨著年齡的增長(zhǎng)和認(rèn)知的不斷變化,可能會(huì)越來越困難。

周末準(zhǔn)備了兩天,這周二到周四約了四個(gè)面試。成都的面試機(jī)會(huì)還是不少的,定向投了點(diǎn),沒有海投,覺得海投意義不大,不如篩選一下。

談一談面試吧。每次面試總會(huì)遇到一些知識(shí)盲區(qū),也算是一些收獲吧。 這四場(chǎng)面試中有純面試,有機(jī)試+面試,也有筆試加面試。機(jī)試沒有當(dāng)場(chǎng)上機(jī),在家寫好就行,我覺得還比較人性化。筆試的話,有一家筆試我做的比較差,算法+編譯期內(nèi)存的一些先后分配,基本全錯(cuò)。這里我承認(rèn)這方面我很薄弱,但我還是想吐槽一下,筆試真的沒必要出這種很帶偏向的題,不應(yīng)該是更注重基礎(chǔ)和平常比較常用的么?例如編譯之后的一些順序,如果不是對(duì)編譯有所了解或者是測(cè)試過,普通的開發(fā)者都是全靠猜,這些題真的意義不大。包括面試中一些很奇葩的題,如何在App的UI中顯示你的git log,我覺得這些題除了刁難一下真沒什么用,當(dāng)然我也有我自己專門研究過比較偏門的,拿出來也很多人不知道,但我面試從來不問,這些沒什么意思。

有兩家我覺得面試的效果還是很好的,直接上名字了:tap4fun和天呈集團(tuán)。前者按簡(jiǎn)歷所述提問,加之一些擴(kuò)展,后者又結(jié)合了點(diǎn)實(shí)際項(xiàng)目。我覺得這種面試官就是會(huì)面試的,去發(fā)現(xiàn)應(yīng)試者會(huì)什么,會(huì)到什么程度,而不是準(zhǔn)備一些奇奇怪怪的題專門問倒應(yīng)試者。

目前收到了兩個(gè)offer,不過和期望薪資還是有點(diǎn)距離,下周再努努力看看。

19.07.22

基礎(chǔ)框架的搭建

一個(gè)項(xiàng)目就像是蓋一棟樓,基礎(chǔ)框架的搭建像地基一樣重要,尤其是對(duì)于團(tuán)隊(duì)開發(fā)來說,地基搭的好多人配合效率就會(huì)很高,反之會(huì)給團(tuán)隊(duì)開發(fā)效率打上很大的折扣。

基礎(chǔ)框架的搭建我覺得主要從以下幾個(gè)方面來說:

1.視圖框架的搭建
2.工具類和基類的抽調(diào)
3.三方庫的選擇
4.編碼的規(guī)范
5.代碼管理的規(guī)范
6.代碼和可視化

這里我比較想先說最后一個(gè),代碼管理的規(guī)范。
我個(gè)人傾向于使用git管理代碼,說一下我覺得比較規(guī)范的管理流程:
1.master主分支
2.任何新的需求都是從master單獨(dú)拉取的分支,即使只是改動(dòng)了一個(gè)字段,也要嚴(yán)格按照功能需求拉取分支;
3.每次發(fā)版建立發(fā)布版本分支,eg:publish_20190722,所有需要在這次版本上線的功能都合并到此分支上,提測(cè)使用此分支;
4.只有publish版本上線成功(通過審核),publish版本才可以合并到master分支;
5.上線成功之后通知其他人員及時(shí)更新master及合并至未上線的功能上;
6.關(guān)于個(gè)人分支是否推送到遠(yuǎn)端,我個(gè)人傾向于推送,防止本地代碼丟失;

19.07.23

繼續(xù)昨天的

1.視圖框架的搭建:
這部分我覺得主要收業(yè)務(wù)驅(qū)動(dòng)加上自身產(chǎn)品的風(fēng)格,大眾化的基本上tabBar+nav就能滿足需求,這里沒有過多的要求,主要看產(chǎn)品的設(shè)計(jì);

2.工具類和基類的抽調(diào)和三方庫的選擇,這兩個(gè)放到一起來說:
同一功能的三方或者工具類只保留一種;
建議由團(tuán)隊(duì)商議后由一個(gè)人單獨(dú)添加;
工具類最好有統(tǒng)一的分類和規(guī)范,不能過于冗雜;

3.編碼的規(guī)范
主要涉及到注釋和命名上,這點(diǎn)不再多說,主要想說下關(guān)于代碼設(shè)計(jì)上的問題。舉個(gè)例子來說,一個(gè)VC如果過于臃腫,我們會(huì)將一部分業(yè)務(wù)邏輯提到model層或者用分類分散代碼。這其中包括很多方面,網(wǎng)絡(luò)server層的放置,view保證復(fù)用的處理方案,model做不做業(yè)務(wù)處理做哪些業(yè)務(wù)處理等等,需要盡可能地作出統(tǒng)一的方案,使整個(gè)項(xiàng)目代碼結(jié)構(gòu)清晰統(tǒng)一。

4.代碼和可視化

19.07.24

項(xiàng)目要不要使用需要學(xué)習(xí)成本的新技術(shù)

這里有個(gè)前提,就是這些技術(shù)是已經(jīng)成熟且經(jīng)過認(rèn)證的技術(shù),比如RAC,而不是剛出來一兩個(gè)月還處在概念階段的技術(shù)。

雖然這類框架有不小的學(xué)習(xí)成本和對(duì)原有代碼層面的入侵,但是我個(gè)人傾向是使用的,例如RAC的方案的引入,綜合學(xué)習(xí)成本和帶來的收益我覺得完全可行,尤其是可以從局部開始使用,再慢慢擴(kuò)大,有一個(gè)循序漸進(jìn)的過程。但類似組件化的框架就不一樣了,這類框架對(duì)原有的體系入侵太大,不建議在非新項(xiàng)目中進(jìn)行使用。

一般來說,如果是團(tuán)隊(duì)開發(fā)就需要考慮到整個(gè)團(tuán)隊(duì)對(duì)新框架的熟悉程度,相應(yīng)地學(xué)習(xí)成本也會(huì)提高,但程序員是需要面對(duì)技術(shù)的更迭的,新技術(shù)的使用不僅是挑戰(zhàn)也是機(jī)會(huì),個(gè)人比較傾向新技術(shù)的使用。

19.07.25

電腦重新裝了一遍cocoapods,記一下目前的坑

重新裝了一遍,發(fā)現(xiàn)有點(diǎn)新坑:(以下均在有梯子的時(shí)候遇到問題)

1.安裝Homebrew遇到的問題
解決方案參考:https://blog.csdn.net/qq_35624642/article/details/79682979

2.執(zhí)行pod setup時(shí)遇到的問題
解決方案參考:http://www.itdecent.cn/p/89211d2ddeac

19.07.26

善用git stash

stash顧名思義貯藏的意思,它的作用是:保存臨時(shí)代碼并清空當(dāng)前所有的文件變更,給出一個(gè)干凈的工作臺(tái)。通常用在當(dāng)前項(xiàng)目還不足以做一次提交又需要一個(gè)干凈的工作臺(tái)來開展新的任務(wù)時(shí),算是一個(gè)比較常用的命令。但在實(shí)際開發(fā)的時(shí)候,我們往往會(huì)隨意commit一次,用此方法去建立干凈的工作臺(tái),一般來說這種方式也沒有太大的錯(cuò)誤,但是會(huì)積累很多無效的commit節(jié)點(diǎn),在回滾或者查看日志的時(shí)候使得整個(gè)flow流冗雜不清晰。團(tuán)隊(duì)開發(fā)的時(shí)候也會(huì)造成很多不必要的麻煩。而stash的方式只會(huì)在本地保存代碼片段而不會(huì)在flow上留下痕跡,使得整個(gè)flow不被污染。

19.07.29

復(fù)雜表單頁面的處理思路

由于移動(dòng)端在日常生活中的使用場(chǎng)景不斷增多,很多像下面這種表單頁面也在逐步向移動(dòng)原生端靠攏:


表單頁面.png

或者比其更復(fù)雜,cell種類更多。這種頁面其實(shí)也不難,流水布局也能寫出來,但想設(shè)計(jì)好卻有些難度。
之前的項(xiàng)目偏向ERP,很多類似這種的表單頁面,總結(jié)下這方面的思路。

1.由于樣式多樣,VC為了去除不必要的if判斷,采用了工廠模式利用model創(chuàng)造對(duì)應(yīng)的cell;
2.模型中的基類為了滿足基本的需要,像下面這樣:

/**
 標(biāo)題
 */
@property (nonatomic,copy) NSString *title;

/**
 副標(biāo)題
 */
@property (nonatomic,copy) NSString *subTitle;

/**
 展示的值
 */
@property (nonatomic,copy) NSString *displayValue;

/**
 提交的值
 */
@property (nonatomic,copy) NSString *submitValue;

/**
 占位
 */
@property (nonatomic,copy) NSString *placeholderValue;

/**
 要實(shí)例化的類名
 */
@property (nonatomic,copy) NSString *className;

以上的字段基本上滿足普通類型的使用

3.cell的基類其實(shí)只做了左邊的標(biāo)題這一個(gè)作用;
4.由于cell樣式多樣,我想盡可能地去除if的判斷,只能選擇字符串映射的方式,或者在基類中提供公共接口,子類返回對(duì)應(yīng)的方法字符串或者selector;
5.數(shù)據(jù)源部分按照模型基類即可,向下面這樣:


數(shù)據(jù)源.png

之前我的寫法是直接寫在文件中,后來發(fā)現(xiàn)這樣會(huì)越來越亂,維護(hù)起來不方便直觀,建議配置plist文件,或者由后臺(tái)返回;
6.交互上,輸入類型進(jìn)行自我校驗(yàn),利用submitKey進(jìn)行判斷需要進(jìn)行校驗(yàn)的方式,這里沒有想到合適的方式去規(guī)避if判斷;

整體框架搭建起來的時(shí)候還是有些麻煩,但是對(duì)于后期的擴(kuò)展和維護(hù)非常友好,邏輯也比較清晰,目前我自己的工程中涉及到表單填寫的部分均用的這種方式。

19.07.30

子類擁有不同的方法實(shí)現(xiàn)

有個(gè)有意思的需求,封裝個(gè)單例類,實(shí)例化之后同一個(gè)方法根據(jù)狀態(tài)不同具有不同的方法實(shí)現(xiàn)。

這個(gè)需求很自然就想到了用runtime去實(shí)現(xiàn):
1.擁有一個(gè)公共調(diào)用方法:funA;
2.真實(shí)需要去實(shí)現(xiàn)的方法:funB,funC,funD...
3.在狀態(tài)改變的時(shí)候,用具體的方法替換掉funA;

當(dāng)然,一般方式也是可以實(shí)現(xiàn)的,我們替換掉上面的第三步:
3.根據(jù)不同的狀態(tài)由funA再去分發(fā)各個(gè)funB,funC,funD;

另外runtime方法替換的方式有一個(gè)弊端,就是只能在單例中做應(yīng)用,因?yàn)榉椒ㄊ侨痔鎿Q的效果,而對(duì)象則會(huì)生成多個(gè),無法應(yīng)用到多對(duì)多的場(chǎng)景。

19.07.31

SEL和IMP

iOS方法調(diào)用.png
C語言函數(shù)調(diào)用.png

從上面的圖中可以看出,OC在方法的調(diào)用上相對(duì)于C語言多了一層SEL的映射,稱之為發(fā)送消息。

當(dāng)然,我們也可以直接拿到一個(gè)方法的IMP指針,直接進(jìn)行IMP的調(diào)用,但為什么要增加一層SEL到IMP的映射呢?

和C語言一樣,OC也是編譯性語言,編譯性語言的弊端就是大部分工作在編譯期間已經(jīng)確定好了,無法在運(yùn)行時(shí)動(dòng)態(tài)地改變。而OC中的runtime改變了這一點(diǎn)。

在方法的調(diào)用上,OC并不直接使用IMP指針,而是增加了一層SEL到IMP的映射,這就為運(yùn)行時(shí)的動(dòng)態(tài)添加了諸多可能性。如果我們單純地使用IMP指針進(jìn)行方法的調(diào)用,在編譯期間已經(jīng)確定了我將調(diào)用哪個(gè)IMP,運(yùn)行時(shí)也很難改變;而添加了一層SEL層的映射,在編譯期間我確定了調(diào)用的SEL,SEL確定IMP,實(shí)際上間接調(diào)用了IMP,而在運(yùn)行期間,我可以改變SEL到IMP的映射關(guān)系,使得A方法可以進(jìn)行B的調(diào)用,大大增加了運(yùn)行時(shí)的動(dòng)態(tài)特性。

19.08.01

swift的值類型和OC中的對(duì)象類型

swift以結(jié)構(gòu)體的方式構(gòu)建了基礎(chǔ)類型,這點(diǎn)和OC的對(duì)象類型有本質(zhì)的差別,那么諸如NSArray和Array這些類型在使用上就會(huì)有很多的不同:值和引用的問題。

為了方便拿數(shù)組類型說事。

OC中的容器類型有深拷貝和淺拷貝一說,如果對(duì)這點(diǎn)沒有什么疑惑,那么對(duì)swift中的值類型賦值也沒有太大問題。

Array是結(jié)構(gòu)體類型,又是容器類,它的賦值相當(dāng)于一次copy,至于是深拷貝還是淺拷貝,要看是用var來修飾還是let來修飾,前者可變而后者不可變,而對(duì)于容器類,無論是深拷貝還是淺拷貝,都是單層相對(duì)于容器來說的,容器內(nèi)部的元素還是之前的元素,并沒有單獨(dú)開辟空間去生成新對(duì)象。

那么這就會(huì)有一些問題:OC中我們可以引用一個(gè)可變數(shù)組去做增刪,對(duì)這個(gè)數(shù)組的操作是對(duì)所有指向該對(duì)象的元素同步的,因?yàn)樵L問的同一內(nèi)存區(qū)域,像下面這樣:

    NSMutableArray *qq = [@[@"dd"] mutableCopy];
    NSMutableArray *cc = qq;
    [cc addObject:@"123"];
    
    NSLog(@"%@ -- %@",qq,cc);
    NSLog(@"%p -- %p",qq,cc);

2019-08-01  Use[50515:1529091] (
    dd,
    123
) -- (
    dd,
    123
)
2019-08-01  Use[50515:1529091] 0x6000024d4570 -- 0x6000024d4570

是一個(gè)同步的操作,而對(duì)于swift就不一樣了,由于容器變化了,我們對(duì)其中一個(gè)容器進(jìn)行增刪對(duì)另外一個(gè)容器就不會(huì)產(chǎn)生影響,像下面這樣:

        let pp = people.init()
        let stu = student.init()
        stu.name = "abc"
        pp.ttArr.append(stu)
        pp.ttArr.append(student.init())
        
        var tempArr = pp.ttArr
        tempArr.append(student.init())
        tempArr[0].name = "123"
        
        print(tempArr)
        print(pp.ttArr.self)
        print(pp.ttArr[0].name)
        print(tempArr[0].name)

[<RotaryTableAnimationDemo.student: 0x600001585e40>, <RotaryTableAnimationDemo.student: 0x600001585f80>, <RotaryTableAnimationDemo.student: 0x600001585f20>]
[<RotaryTableAnimationDemo.student: 0x600001585e40>, <RotaryTableAnimationDemo.student: 0x600001585f80>]
123
123

可以看到tempArr比pp.ttArr多了一個(gè)元素,說明兩者已經(jīng)是不同的容器。值得注意的是我們?cè)趯?duì)tempArr數(shù)組中第一個(gè)元素進(jìn)行修改的時(shí)候,pp.ttArr第一個(gè)元素也產(chǎn)生了相應(yīng)的變化,說明容器內(nèi)部的元素還是引用的方式。

這些問題可能會(huì)出現(xiàn)在多個(gè)頁面共用同一數(shù)據(jù)源的情況,值得注意一下。

最后編輯于
?著作權(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)容

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