談談分布式事務之三: System.Transactions事務詳解[下篇]

在前面一篇給出的Transaction的定義中,信息的讀者應該看到了一個叫做DepedentClone的方法。該方法對用于創建基于現有Transaction對 象的“依賴事務(DependentTransaction)”。不像可提交事務是一個獨立的事務對象,依賴事務依附于現有的某個事務(可能是可提交事 務,也可能是依賴事務)。依賴事務可以幫助我們很容易地編寫一些事務型操作,當環境事務不存的時候,可以確保操作在一個獨立的事務中執行;當環境事務存在 的時候,則自動加入其中。

一、依賴事務(Dependent Transaction)

依賴事務通過DependentTransaction類型表示,DependentTransaction定義如下。和CommittableTransaction一樣,DependentTransaction也是Transaction的子類。既然DependentTransaction依賴于現有的Transaction對象而存在,相當于被依賴事務的子事務,所以無法執行對事務的提交,也自然不會定義Commit方法。但是,DependentTransaction具有一個唯一的方法成員:Complete。調用這個方法意味著向被依賴事務發送通知,表明所有與依賴事務相關的操作已經完成。

   1: [Serializable]
   2: public sealed class DependentTransaction : Transaction
   3: {  
   4:     public void Complete();
   5: }

1、通過DependentTransaction將異步操所納入現有事務

通過Transaction的 靜態屬性Current表示的環境事務保存在TLS(Thread Local Storage)中,所以環境事務是基于當前線程的。這就意味著,即使環境事務存在,通過異步調用的操作也不可能自動加入到當前事務之中,因為在異步線程 中感知不到環境事務的存在。在這種情況下,我們需要做的就是手工將當前事務傳遞到另一個線程中,作為它的環境事務。通過依賴事務我們很容易實現這一點。

DependentTransaction通過Transaction的DependentClone方法創建,該方法具有一個DependentCloneOption枚舉類型的參數,體現了被依賴的事務再上尚未接受到依賴事務的通知(調用Complete或者Rollback方法)得情況下,提交或者完成所采取的事務控制行為。DependentCloneOption提供了兩個選項,BlockCommitUntilComplete表示被依賴事務會一直等待接收到依賴事務的通知或者超過事務設定的超時時限;而RollbackIfNotComplete則會直接將被依賴的事務回滾,并拋出TransactionAbortedException異常。

   1: [Serializable]
   2: public class Transaction : IDisposable, ISerializable
   3: {       
   4:     //其他成員
   5:     public DependentTransaction DependentClone(DependentCloneOption cloneOption);
   6: }
   7: public enum DependentCloneOption
   8: {
   9:     BlockCommitUntilComplete,
  10:     RollbackIfNotComplete
  11: }

下面的代碼演示了如果通過依賴事務,采用異步的方式進行銀行轉賬操作。借助于組件ThreadPool,將主線程環境事務的依賴事務傳遞給異步操作代理,開始異步操作的時候將此依賴事務作為當前的環境事務,那么之后的操作將自動在當前事務下進行。

   1: private static void Transfer(string accountFrom, string accountTo, double amount)
   2: {
   3:     Transaction originalTransaction = Transaction.Current;
   4:     CommittableTransaction transaction = new CommittableTransaction();
   5:     try
   6:     {
   7:         Transaction.Current = transaction;
   8:         ThreadPool.QueueUserWorkItem(state =>
   9:         {
  10:             Transaction.Current = state as DependentTransaction;
  11:             try
  12:             {
  13:                 Withdraw(accountFrom, amount);
  14:                 Deposite(accountTo, amount);
  15:                 (state as DependentTransaction).Complete();
  16:             }
  17:             catch (Exception ex)
  18:             {
  19:                 Transaction.Current.Rollback(ex);
  20:             }
  21:             finally
  22:             {
  23:                 (state as IDisposable).Dispose();
  24:                 Transaction.Current = null;
  25:             }
  26:         }, Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete));
  27:         //其他操作
  28:         transaction.Commit();
  29:     }
  30:     catch (TransactionAbortedException ex)
  31:     {
  32:         transaction.Rollback(ex);
  33:         Console.WriteLine("轉帳失敗,錯誤信息:{0}", ex.InnerException.Message);
  34:     }
  35:     catch (Exception ex)
  36:     {
  37:         transaction.Rollback(ex);
  38:         throw;
  39:     }
  40:     finally
  41:     {
  42:         Transaction.Current = originalTransaction;
  43:         transaction.Dispose();
  44:     }
  45: }

由于在調用DependentClone方法創建依賴事務時指定的參數為 DependentCloneOption.BlockCommitUntilComplete,所以主線程在調用Commit方法提交事務的時候,由于 依賴事務尚未結束(調用Complete或者Rollback方法),在這里會一直等待。如果依賴事務的Complete或者Rollback一直沒有調 用,那么被依賴的事務會一直等到超出事務設置的超時時限。所以,對于基于BlockCommitUntilComplete選項創建的依賴事務來說,應該 及時地調用Complete或者Rollback方法。

2、通過DependentTransaction實現事務型方法

這里所說的事務型方法是指方法的執行總是在事務中執行。具體來講,有兩種不同的事務應用場景:如果當前不存在環境事務,那么方法的執行將在一個獨立的事務中執行;反之,如果存在環境事務,在方法執行會自動加入到環境事務之中。

比如說,存儲(Deposit)和提取(Withdraw)就是典型的事務型操作。對于單純的存取款的場景,應該創建一個新的事務來控制存儲和提取 操作的執行,以確保單一帳戶款項的數據一致性。如果在轉賬的場景中,應在在轉賬開始之前就創建一個新的事務,讓提取和存儲的操作自動加入到這個事務之中。

我們現在就結合可提交事務和依賴事務將Deposit和Withdraw兩個方法定義成事務型方法,為了相同代碼的重復,在這里把事務控制部分定義在如下一個InvokeInTransaction靜態方法中:

   1: static void InvokeInTransaction(Action action)
   2: {
   3:     Transaction originalTransaction = Transaction.Current;
   4:     CommittableTransaction committableTransaction = null;
   5:     DependentTransaction dependentTransaction = null;
   6:     if (null == Transaction.Current)
   7:     {
   8:         committableTransaction = new CommittableTransaction();
   9:         Transaction.Current = committableTransaction;
  10:     }
  11:     else
  12:     {
  13:         dependentTransaction = Transaction.Current.DependentClone(DependentCloneOption.RollbackIfNotComplete);
  14:         Transaction.Current = dependentTransaction;
  15:     } 
  16:? 
  17:     try
  18:     {
  19:         action();
  20:         if (null != committableTransaction)
  21:         {
  22:             committableTransaction.Commit();
  23:         } 
  24:? 
  25:         if (null != dependentTransaction)
  26:         {
  27:             dependentTransaction.Complete();
  28:         }
  29:     }
  30:     catch (Exception ex)
  31:     {
  32:         Transaction.Current.Rollback(ex);
  33:         throw;
  34:     }
  35:     finally
  36:     {
  37:         Transaction transaction = Transaction.Current;
  38:         Transaction.Current = originalTransaction;
  39:         transaction.Dispose();
  40:     }
  41: }

InvokeInTransaction方法的參數是一個Action類型的代理(Delegate),表示具體的業務操作。在開始的時候記錄下當 前的環境事務,當整個操作結束之后應該環境事務恢復成該值。如果存在環境事務,則創建環境事務的依賴事務,反之直接創建可提交事務。并將新創建的依賴事務 或者可提交事務作為當前的環境事務。將目標操作的執行(action)放在try/catch中,當目標操作順利執行后,調用依賴事務的Complete 方法或者可提交事務的Commit方法。如果拋出異常,則調用環境事務的Rollback進行回滾。在finally塊中將環境事務恢復到之前的狀態,并 調用Dispose方法對創建的事務進行回收。

借助于InvokeInTransaction這個輔助方法,我們以事務型方法的形式定義了如下的兩個方法:Withdraw和Deposit,分別實現提取和存儲的操作。

   1: static void Withdraw(string accountId, double amount)
   2: {           
   3:     Dictionary<string, object> parameters = new Dictionary<string, object>();
   4:     parameters.Add("id", accountId);
   5:     parameters.Add("amount", amount);
   6:     InvokeInTransaction(() => DbAccessUtil.ExecuteNonQuery("P_WITHDRAW", parameters));
   7: }       
   8:? 
   9: static void Deposit(string accountId, double amount)
  10: {
  11:     Dictionary<string, object> parameters = new Dictionary<string, object>();
  12:     parameters.Add("id", accountId);
  13:     parameters.Add("amount", amount);
  14:     InvokeInTransaction(() => DbAccessUtil.ExecuteNonQuery("P_DEPOSIT", parameters));
  15: }

二、TransactionScope

在上面一節,我結合可提交事務和依賴事務,以及環境事務的機制提供了對事務型操作的實現。實際上,如果借助TransactionScope,相應的代碼將會變得非常簡單。下面的代碼中,通過TransactionScope對InvokeInTransaction進行了改寫,從執行效果來看這和原來的代碼完全一致。

   1: static void InvokeInTransaction(Action action)
   2: {
   3:     using (TransactionScope transactionScope = new TransactionScope())
   4:     {
   5:         action();
   6:         transactionScope.Complete();
   7:     }
   8: }

通過InvokeInTransaction方法前后代碼的對比,我們可以明顯看到TransactionScope確實能夠使我們的事務控制變得非常的簡單。實際上,在利用System.Transactions事務進行編程的時候,我們一般不會使用到可提交事務,對于依賴事務也只有在異步調用的時候會使用到,基于TransactionScope的事務編程方式才是我們推薦的。

正如其名稱所表現的一樣,TransactionScope就是為一組事務型操作創建一個執行范圍,而這個范圍始于TransactionScope創建之時,結束于TransactionScope被回收(調用Dispose方法)。在對TransactionScope進行深入介紹之前,照例先來看看它的定義:

   1: public sealed class TransactionScope : IDisposable
   2: {
   3:     public TransactionScope();
   4:     public TransactionScope(Transaction transactionToUse);
   5:     public TransactionScope(TransactionScopeOption scopeOption);
   6:     public TransactionScope(Transaction transactionToUse, TimeSpan scopeTimeout);
   7:     public TransactionScope(TransactionScopeOption scopeOption, TimeSpan scopeTimeout);
   8:     public TransactionScope(TransactionScopeOption scopeOption, TransactionOptions transactionOptions);
   9:     public TransactionScope(Transaction transactionToUse, TimeSpan scopeTimeout, EnterpriseServicesInteropOption interopOption);
  10:     public TransactionScope(TransactionScopeOption scopeOption, TransactionOptions transactionOptions, EnterpriseServicesInteropOption interopOption);
  11:     
  12:     public void Complete();
  13:     public void Dispose();    
  14: }

我們可以看到TransactionScope實現了IDisposable接口,除了Dispose方法之外,僅僅具有一個唯一的方法:Complete。但是TransactionScope卻有一組豐富的構造函數。我們先來看看這些構造函數相應的參數如何影響TransactionScope對事務控制的行為。

1、TransactionScopeOption

實際上前面一節中提供的InvokeInTransaction方法基本上體現了TransactionScope的內部實現。也就是說,TransactionScope也是通過創建可提交事務或者依賴事務,并將其作為事務范圍內的環境事務,從而將范圍的所有操作納入到一個事務之中。

通過在構造函數中指定TransactionScopeOption類型的scopeOption參數,控制TransactionScope當環境事務存在的時候應該采取怎樣的方式執行事務范圍內的操作。具有來講,具有三種不同的方式:

  • 如果已經存在環境事務,則使用該環境事務。否則,在進入范圍之前創建新的事務;
  • 總是為該范圍創建新事務;
  • 環境事務上下文在創建范圍時被取消。范圍中的所有操作都在無環境事務上下文的情況下完成。

TransactionScopeOption是一個枚舉,三個枚舉值Required、RequiresNew和Suppress依次對應上面的三種行為。

   1: public enum TransactionScopeOption
   2: {
   3:     Required,
   4:     RequiresNew,
   5:     Suppress
   6: }

對于Required選項,如果當前存在環境事務TransactionScope會 創建環境事務的依賴事務,負責創建可提交事務,然后將創建的環境事務或者可提交事務作為事務范圍的環境事務。如對于RequiresNew選 項,TransactionScope總是會創建可提交事務并將其作為事務范圍的環境事務,意味著控制事務范圍內操作的事務也當前的環境事務已經沒有任何 關系。如果Suppress選項,TransactionScope會將事務范圍內的環境事務設為空,意味著事務范圍內的操作并不受事務的控制。

Required是默認選項,意味著事務范圍內的事務將會作為當前環境事務的一部分。如果你不希望某個操作被納入當前的環境事務,但是相應的操作也 需要事務的控制以確保所操作數據的一致性。比如,當業務邏輯失敗導致異常拋出,需要對相應的錯誤信息進行日志記錄。對于日記的操作就可以放入基于RequiresNew選項創建TransactionScope中。對于一些不重要的操作(操作的錯誤可被忽略),并且不需要通過事務來控制的操作,比如發送一些不太重要的通知,就可以采用Suppress選項。

2、TransactionOptions和EnterpriseServicesInteropOption

TransactionOptions在前面已經提及,用于控制事務的超時時限和隔離級別。對于超時時限,你也可以選擇 TransactionScope相應能夠的構造函數以TimeSpan的形式指定。而對于事務的隔離級別,需要著重強調一點:當選擇 TransactionScopeOption.Required選項時,TransactionScope指定的隔離級別必須與環境事務(如果有)相匹 配。

比如下面的例子中,我定義兩個嵌套的TransactionScope,外部的TransactionScope采用默認的隔離級別,內部在采用ReadCommitted隔離級別,當執行這段代碼的時候,會拋出如圖1所示的ArgumentException異常。

   1: using (TransactionScope outerScope = new TransactionScope())
   2: {
   3:     TransactionOptions transactionOptions = new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted };
   4:     using (TransactionScope innerScope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
   5:     {
   6:         //事務型操作
   7:         innerScope.Complete();
   8:     }
   9:     //事務型操作
  10:     outerScope.Complete();
  11: }

image

圖1 隔離級別不一致導致的異常

實際上在System.Transactions事務機制被引入之前,像Enterprise Service主要依賴于基于COM+的分布式事務。TransactionScope通過 EnterpriseServicesInteropOption控制System.Transactions事務如何與COM+的分布式事務進行互操 作。具有來講具有如下三種互操作選項,分別和EnterpriseServicesInteropOption三個枚舉值相對應:

  • None:Transaction 和 Current 之間不同步;
  • Automatic:搜索現有的 COM+ 上下文并與之同步(如該上下文存在);
  • Full:System.EnterpriseServices 上下文(可通過調用 ContextUtil 類的靜態方法 Transaction 來檢索)和 System.Transactions 環境事務(可通過調用 Transaction 類的靜態方法 Current 來檢索)始終保持同步。這將引入性能損失,因為可能需要創建新的 System.EnterpriseServices 上下文。
   1: public enum EnterpriseServicesInteropOption
   2: {
   3:     None,
   4:     Automatic,
   5:     Full
   6: }

3、事務提交和回滾

對于事務范圍中的事務,無論是事務的提交(對于可提交事務)、完成(依賴事務)和回滾都是在Dispose方法中執行的。 TransactionScope中定一個個私有的布爾類型字段(complete)表示事務是否正常結束。該成員的默認值為False,當調用 TransactionScope的Complete方法的時候會將此字段設置成True。當Dispose執行的時候,如果該字段的值為False,會 調用事務的Rollback方法對該事務實施回滾;否則會調用Commit方法(對于可提交事務)對事務進行提交或者調用Complete方法(依賴事 務)通知被依賴的事務本地事務已經正常完成。

除了執行事務的提交、完成或者回滾之外,TransactionScope的Dispose方法還負責將環境事務回復到事務范圍開始之前的狀態。在 調用Complete和Dispose之前,環境事務處于不可用的狀態,如果此時試圖獲取環境事務,會拋出異常。比如在下面的代碼中,在事務范圍內部調用 Complete方法后,通過Transaction的Current靜態屬性獲取當前環境事務,會拋出圖2所示的InvalidOpertionException異常。

   1: using (TransactionScope transactionScope = new TransactionScope())
   2: {
   3:     //其他事務操作
   4:     transactionScope.Complete();
   5:     Transaction ambientTransaction = Transaction.Current;
   6: }  

image?圖2 在TransactionScope完成之后獲取環境事務導致的異常

轉載于:https://www.cnblogs.com/duanxz/p/4282006.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/257644.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/257644.shtml
英文地址,請注明出處:http://en.pswp.cn/news/257644.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

HDU——2444 The Accomodation of Students

The Accomodation of Students Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)                    Total Submission(s): 7086 Accepted Submission(s): 3167 Problem DescriptionThere are a group of studen…

iOS開發系列--觸摸事件、手勢識別、搖晃事件、耳機線控

-- iOS事件全面解析 概覽 iPhone的成功很大一部分得益于它多點觸摸的強大功能&#xff0c;喬布斯讓人們認識到手機其實是可以不用按鍵和手寫筆直接操作的&#xff0c;這不愧為一項偉大的設計。今天我們就針對iOS的觸摸事件&#xff08;手勢操作&#xff09;、運動事件、遠程控制…

關于Hyper-V備份的四大注意事項

盡管Hyper-V備份相對簡單&#xff0c;但備份管理員仍需注意四大問題。這四方面的問題在創建備份時可能不太重要&#xff0c;但在備份恢復時影響甚大。 1、對于虛擬機來說不僅意味著虛擬磁盤 就目前來看&#xff0c;企業在執行Hyper-V備份時最常見的誤區就是把虛擬機當做物理服務…

python為什么忽然火了_為什么Python突然就火了起來了呢?

近日&#xff0c;TIOBE發布10月編程語言排行榜顯示&#xff0c;15年來TIOBE指數的前8名一直保持不變&#xff0c;而Python正在成為一種新的大型語言。越來越多的企業在使用Python進行開發&#xff0c;越來越多的人正在加入Python程序員行列!TIOBE 10月編程語言排行榜前20名Pyth…

SQL 2005 全文索引

全文索引技術是目前搜索引擎的關鍵技術。 試想在1M大小的文件中搜索一個詞&#xff0c;可能需要幾秒&#xff0c;在100M的文件中可能需要幾十秒&#xff0c;如果在更大的文件中搜索那么就需要更大的系統開銷&#xff0c;這樣的開銷是不現實的。 所以在這樣的矛盾下出現了全文索…

python重命名窗口_Python:即時重命名方法名稱

如果要繼續在已切換到使用屬性的對象上使用get_Field和set_Field(您只需訪問或分配給Field),則可以使用包裝器對象&#xff1a;class NoPropertyAdaptor(object):def __init__(self, obj):self.obj objdef __getattr__(self, name):if name.startswith("get_"):retu…

nginx優化之請求直接返回json數據

對于有些服務端接口返回是固定值的json&#xff0c;可通過配置nginx直接返回json&#xff0c;減少程序的加載對資源的占用&#xff0c;減少接口響應時間 location ~* (request/update)$ { default_type application/json; return 200 {"update":"no&quo…

ARP掃描工具arp-scan

2019獨角獸企業重金招聘Python工程師標準>>> ARP掃描工具arp-scan arp-scan是Kali Linux自帶的一款ARP掃描工具。該工具可以進行單一目標掃描&#xff0c;也可以進行批量掃描。批量掃描的時候&#xff0c;用戶可以通過CIDR、地址范圍或者列表文件的方式指定。該工具…

數據庫索引的作用和優點缺點

為什么要創建索引呢&#xff1f;這是因為&#xff0c;創建索引可以大大提高系統的性能。 第一&#xff0c;通過創建唯一性索引&#xff0c;可以保證數據庫表中每一行數據的唯一性。 第二&#xff0c;可以大大加快 數據的檢索速度&#xff0c;這也是創建索引的最主要的原因。 第…

elementui el-from 怎樣顯示圖片_vue2.0使用weui.js的uploader組件上傳圖片(兼容移動端)...

本文已同步到專業技術網站 www.sufaith.com, 該網站專注于前后端開發技術與經驗分享, 包含Web開發、Nodejs、Python、Linux、IT資訊等板塊.最近在使用 vue2.0開發微信公眾號網頁 其中涉及到 選擇圖片, 圖片的壓縮上傳, 預覽, 刪除等操作。項目整體UI框架使用的是 vux, 但可惜的…

面向對象分析

在需求獲取階段&#xff0c;開發人員關注于理解用戶以及他們的使用要求。而在需求分析階段&#xff0c;開發人員關注于理解系統需要構建的內容&#xff0c;其核心是產生一個準確的、完整的、一致的和可驗證的系統模型&#xff0c;稱為分析模型。 面對對象的分析模型由三個獨立的…

python字典輸入學生信息_如何用Python將XML中的所有信息輸入字典

我通常使用標準庫中的ElementTree模塊解析XML。它沒有給你一個字典&#xff0c;你得到了一個更有用的DOM結構&#xff0c;它允許你為孩子們遍歷每個元素。from xml.etree import ElementTree as ETxml ET.parse("root_element xml.getroot()for child in root_element:.…

HDU4267(2012年長春站)

這道題真的是好題&#xff0c;讓我對線段樹有了全新的認識&#xff0c;至少讓我真正感受到了線段樹的神奇。 題意是就是線段樹區間更新&#xff0c;單點詢問的問題&#xff0c;不過這個題好就好在它的區間更新的點并不連續&#xff01; adding c to each of Ai which satisfies…

ITFriend創業敗局(四):菜鳥CEO的自我修養

自創業自封CEO以來&#xff0c;短短3個月&#xff0c;又經歷了無數的磨練&#xff0c;快速成長中。創業不同于打工&#xff0c;他要求你必須有全局觀和綜合能力&#xff0c;技術、市場、商務&#xff0c;啥都得會&#xff0c;還要處理各種各樣的問題和矛盾。根據個人經歷&#…

51nod 1050 循環數組最大子段和

1050 循環數組最大子段和 N個整數組成的循環序列a[1],a[2],a[3],…,a[n]&#xff0c;求該序列如a[i]a[i1]…a[j]的連續的子段和的最大值&#xff08;循環序列是指n個數圍成一個圈&#xff0c;因此需要考慮a[n-1],a[n],a[1],a[2]這樣的序列&#xff09;。當所給的整數均為負數時…

mysql設置token有效期_記住我 token保存到數據庫

記住我 token保存到數據庫這里使用jpamysqlorg.springframework.bootspring-boot-starter-data-jpamysqlmysql-connector-javaspring.datasource.driver-class-namecom.mysql.cj.jdbc.Driverspring.datasource.urljdbc:mysql://127.0.0.1:3306/fly-demo?serverTimezoneUTC&…

Spark- Linux下安裝Spark

Spark- Linux下安裝Spark 前期部署 1.JDK安裝&#xff0c;配置PATH 可以參考之前配置hadoop等配置 2.下載spark-1.6.1-bin-hadoop2.6.tgz,并上傳到服務器解壓 [rootsrv01 ~]# tar -xvzf spark-1.6.1-hadoop2.6.tgz /usr/spark-1.6.1-hadoop2.6 3.在 /usr 下創建軟鏈接到目標文…

Linux Apache 怎么修改工作模式

Apache默認為prefork模式&#xff0c;主要是考慮到穩定性的原因。  要切換到worker模式&#xff0c;則需要登錄到linux上&#xff0c;進行如下操作&#xff1a;  進入/usr/sbin目錄  cd /usr/sbin  將當前的prefork模式啟動文件改名  mv httpd httpd.prefork  將wo…

python需要背的英語單詞怎么寫_學Python必須背的42個常見單詞,看看你都會嗎?...

這42個單詞是學習Python必須背會的單詞&#xff0c;也是代碼中常見的單詞。希望你能都背下來&#xff01;&#xff01;1. adult [?d?lt] 成年人2. authentication [???θent??ke??n] 身份驗證、認證、鑒定3. bit [b?t] 稍微、小量、小塊、一點4. byte [ba?t] …

viewDidLoad、viewWillAppear、viewWillDisappear

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil viewDidLoad viewWillAppear viewWillDisapppear《iOS編程》P137關于視圖的初始化代碼不能寫在視圖控制器的初始化&#xff08;1&#xff09;&#xff0c;原因如下&#xff1a;為…