Can't add self as subview解析

轉場過程解析

UINavigationController對于translation動畫做了一定的封裝, 同時持有fromAnimateView與toAnimateView, 在進行translation動畫時將對應的VC的view掛載到對應的AnimateView上, 動畫視圖AnimateView又掛載到容器視圖wrapperView, UINavigationController只需控制容器中的AnimateView實現(xiàn)相應translation動畫, translation動畫完成后, 移除動畫視圖并掛載棧頂?shù)囊晥D, 實現(xiàn)navigationController對外部進行了動畫隔離.

A push to B (transition)
A push to B (complete)
B pop to A (transition)

Can't add self as subview 復現(xiàn)

模擬車禍:

pushNoAnimate(@"A");
pushAnimate(@“B");
pushAnimate(@“C”);
同時執(zhí)行完以上操作(即上一個還沒執(zhí)行完畢就同步執(zhí)行后續(xù)操作), 之后的pop退場操作會導致車禍

車禍現(xiàn)場:

轉場動畫中toAnimateView加載到WrapperView這一步驟

車禍前現(xiàn)象:

pushC, C成功入棧, 但是視圖沒有加載到容器中, 實際顯示的還是B的vc與view, 但是棧頂是C的vc

車禍分析:
  • 第一次點返回時(實際應該C的vc出棧), 當前視圖(B的view)被先后加載到fromAnimateView與toAnimateView上, 原本視圖在出棧完成后應該被釋放, 但是容器棧內還存在B的vc, 故保留了
  • 第二次點返回時(實際應該B的vc出棧), A的view加載到toAnimateView上, 隨后toAnimateView需要加載到wrapperView進行transition動畫, 但wrapperView通過棧頂元素view.superview取值, 而棧頂元素B的view由于上一次錯誤的轉場, 并未在transition動畫完成后掛載到wrapperView, 還保留在的臨時的動畫視圖toAnimateView上, 所以使toAnimateView加載到WrapperView的操作變成了動畫視圖toAnimateView加載到自己上
時序分析:
A push B

A.view -> From Animation View
B.view -> To Animation View
A.view -> Wrap View
B.view -> Wrap View
A.view -> Nil

B pop A

A.view -> To Animation View
B.view -> From Animation View
B.view -> Wrap View
A.view -> Wrap View
B.view -> Nil

  • A Push B No animation
  • B Push C animation
  • C Push D animation
    由于B是無動畫的,使C嘴秸、D的視圖動畫沒按原有的隊列執(zhí)行是钥,一起執(zhí)行而導致沖突,只完成C的動畫对省,并觸發(fā)警告“nested push animation can result in a corrupted navigation bar
    Attempting to begin a transition on navigation bar while a transition is in progress”。
  • D Pop C
    C.view -> To Animation View,而D的view由于上述動畫沖突不在視圖棧中,也使Pop動畫終止狂票。
  • C Pop B
    B.view -> To Animation View,隨后To Animation View需要加到Wrap View中熙暴,而Wrap View的獲取通過棧頂view.superview獲取闺属,即C.view.superview(To Animation View),觸發(fā)了To Animation View -> To Animation View周霉。

  • 思路一:

使用delegate

- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController(UINavigationController *)navigationController animationControllerForOperation (UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC

自定義所有轉場動畫, 規(guī)避系統(tǒng)進行轉場動畫時的錯誤視圖加載.

問題:

當錯誤case產生時, transitioningController中取到的containerView取值為nil, 只是跳過了這一次轉場動畫, 而實際的錯誤轉場現(xiàn)象并未解決, 如以上車禍模擬, pushC成功入棧, 但是視圖沒有加載到容器中, 依舊展示的是B的vc與view.

重新定位問題:

轉場動畫時的錯誤視圖加載的根本原因是連續(xù)非正常的轉場, class-dump出UINavigationController有相應的defer transition的屬性與API, navigationController對連續(xù)轉場做了一定流程的控制

連續(xù)轉場的時序圖如下, 后續(xù)兩個transition都被defer, 后續(xù)統(tǒng)一觸發(fā)
UINavigationController暴露給外部調用的push/pop方法實際只是一個“轉場請求”, 對于連續(xù)轉場navigationController會統(tǒng)一調度這些“請求”


連續(xù)轉場的時序圖

系統(tǒng)Bug:
無動畫的轉場也需要完成一些切換vc, 重新掛載view等操作, 而在執(zhí)行這些操作的同時, 后續(xù)觸發(fā)的“轉場請求”會根據(jù)當前正在執(zhí)行的轉場判斷是否需要被加到deffer的隊列中, 所以無動畫的轉場的后續(xù)轉場操作會同步執(zhí)行, 從而導致轉場異常.

  • 思路二:

模擬車禍的的路徑中, 都有無動畫的轉場, 在私有方法中根據(jù)transition參數(shù), 判斷是否為有動畫的轉場, 對于無動畫的轉場強制立刻執(zhí)行, 使它不影響后續(xù)的defer transition. (transition: 1為有動畫push, 2為有動畫pop, 0 為無動畫)

- (void)_pushViewController:(id)arg1 transition:(int)arg2 forceImmediate:(_Bool)arg3
問題:

在低端機(iOS8)上, 連續(xù)push三次也會導致轉場異常.

  • 思路三:

導致轉場異常的根本原因是上一個次操作還沒執(zhí)行結束就開始執(zhí)行下一個操作, 同步執(zhí)行了多個轉場操作, 根據(jù)私有屬性wasLastOperationAnimated判斷上一個操作是否還在動畫中, 對于上一個次操作還沒執(zhí)行結束就開始執(zhí)行下一個操作的case, 直接clear之前的轉場操作, 但clear操作不能在發(fā)送“轉場請求”時執(zhí)行, 時機太早UINavigationController還沒進行defer transition的處理, 這里需要在UINavigationController進行defer transition的處理失敗后并在觸發(fā)轉場動畫前進行clear(vc已入棧, 只clear轉場的動畫), 即思路二中函數(shù)調用的時機, 在其中進行非正常轉場的clear操作.

問題:

clear操作后, 異常轉場之前還未執(zhí)行或正常執(zhí)行的轉場動畫會被取消, 直接展示最后棧頂元素.


結論:

hook私有API 獲取觸發(fā)轉場動畫前的時機, 在每次觸發(fā)轉場動畫前判斷上一次是否完成, 對于異常情況進行_clearLastOperation操作
取消之前的轉場過程保護, 保證業(yè)務邏輯正常跳轉

- (void)ac_pushViewController:(id)viewController transition:(int)transition forceImmediate:(_Bool)force {
    BOOL needClear = [self ac_checkTransition];
    if (needClear) {
        [self ac_clearOperation];
    }
    [self ac_pushViewController:viewController transition:transition forceImmediate:force];
}

- (id)ac_popViewControllerWithTransition:(int)transition allowPoppingLast:(_Bool)allowPoppingLast {
    BOOL needClear = [self ac_checkTransition];
    id value = [self ac_popViewControllerWithTransition:transition allowPoppingLast:allowPoppingLast];
    if (needClear) {
        [self ac_clearOperation];
    }
    return value;
}

- (id)ac_popToViewController:(id)viewController transition:(int)transition {
    BOOL needClear = [self ac_checkTransition];
    id value = [self ac_popToViewController:viewController transition:transition];
    if (needClear) {
        [self ac_clearOperation];
    }
    return value;
}

- (BOOL)ac_checkTransition {
    bool lastOperationAnimated = NO;
    //獲取last opertaion 是否還在轉場動畫中
    SEL lastOperationSEL =  NSSelectorFromString(@"wasLastOperationAnimated");
    if ([self respondsToSelector:lastOperationSEL]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        lastOperationAnimated = [self performSelector:lastOperationSEL];
#pragma clang diagnostic pop
    }
    return lastOperationAnimated;
}

- (void)ac_clearOperation {
    //只是clear轉場動畫, navigation堆棧依舊保持原樣
    SEL clearLastOperationSEL = NSSelectorFromString(@"_clearLastOperation");
    if ([self respondsToSelector:clearLastOperationSEL]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:clearLastOperationSEL];
#pragma clang diagnostic pop
    }
}

風險:

hook私有API 3個, 調用私有API 2個

Ref:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末掂器,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子俱箱,更是在濱河造成了極大的恐慌唉匾,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異巍膘,居然都是意外死亡厂财,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門峡懈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來璃饱,“玉大人,你說我怎么就攤上這事肪康〖远瘢” “怎么了?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我帕胆,道長觉鼻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘辩蛋。我一直安慰自己,他們只是感情好移盆,可當我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布悼院。 她就那樣靜靜地躺著,像睡著了一般咒循。 火紅的嫁衣襯著肌膚如雪据途。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天叙甸,我揣著相機與錄音昨凡,去河邊找鬼。 笑死蚁署,一個胖子當著我的面吹牛便脊,可吹牛的內容都是我干的。 我是一名探鬼主播光戈,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼哪痰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了久妆?” 一聲冷哼從身側響起晌杰,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎筷弦,沒想到半個月后肋演,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抑诸,經...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年爹殊,在試婚紗的時候發(fā)現(xiàn)自己被綠了蜕乡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡梗夸,死狀恐怖层玲,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情反症,我是刑警寧澤辛块,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站铅碍,受9級特大地震影響润绵,放射性物質發(fā)生泄漏。R本人自食惡果不足惜胞谈,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一尘盼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呜魄,春花似錦悔叽、人聲如沸莱衩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽笨蚁。三九已至睹晒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間括细,已是汗流浹背伪很。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奋单,地道東北人锉试。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像览濒,于是被迫代替她去往敵國和親呆盖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內容