1、通知傳值
首先我們來看看通知傳值,通知可實現任意界面之間的數據傳遞,但必須滿足一個條件,就是保證在發送通知的時候監聽者已經存在(先要注冊通知)。而通知的注冊主要通過NSNotificationCenter通知中心實現,其為一個系統單例,系統提供了defaultCenter()方法獲取通知實例對象。
通知使用步驟:注冊通知 -> 發送通知 -> 移除通知
通知實現的原理,我們可以這樣去理解,學生監聽下課鈴聲。我們把學生看做監聽者(或者叫觀察者),監聽鈴聲,鈴聲一響就放學。當鈴聲響起時,我們看做發出一個通知(信號),學生在監聽到鈴聲之后就會做出相應的操作,比如放學之后做什么……
接下來我們看看通知傳值的具體實現方式。這里我們模擬從詳情界面傳值到主界面,為了統一,下文都用ViewController表示主界面,DetailViewController表示詳情界面。首先我們需要在主界面注冊通知,因為,程序運行肯定是先到主界面中,所以,當在詳情界面發送通知的時候,通知監聽者肯定是存在的。注冊通知的方法常用的有以下兩種:
// 1、publicfunc addObserver(observer: AnyObject, selector aSelector: Selector, name aName:String?, object anObject: AnyObject?)
// 2、publicfunc addObserverForName(name:String?, object obj: AnyObject?,queue: NSOperationQueue?, usingBlock block: (NSNotification)->Void)->NSObjectProtocol
第1種,我們需要通過Selector參數設置接收到通知時觸發的方法。而第2種,我們無需關聯觸發方法,在方法尾部跟著一個閉包,當接收到通知的時候該閉包會自動調用,我們可直接在閉包內處理相應的邏輯即可。第2種方法還有一個參數queue,該參數主要設置通知觸發方法執行的隊列,其為NSOperationQueue類型的對象,這里我們一般在主隊列執行,配置參數方法為NSOperationQueue.mainQueue()。我們可以直觀的看到,在兩種方法中都有一個name參數,該參數我們可以理解為通知的代號,通過這個代號我們可以避免多個通知串聯,這個參數我們可以賦值任意字符串。此處以第2種為例。
//1、register notification
NSNotificationCenter.defaultCenter().addObserverForName("notification_name", object: nil, queue: NSOperationQueue.mainQueue()) { (info: NSNotification) -> Voidin// 處理接收到通知之后執行的邏輯...}
通知注冊好之后,下一步我們就可以在詳情界面發送通知了,我們在處理界面跳轉(返回)的方法中處理這一邏輯。發送通知主要用到以下方法:
// 發送通知publicfuncpostNotificationName(aName: String,objectanObject: AnyObject?, userInfo aUserInfo: [NSObject : AnyObject]?)
這里需要注意,發送通知的aName參數,必須和注冊通知時的name參數一致,否則在主界面將無法接收到通知。我們可通過aUserInfo參數將需要傳遞的數據傳遞到主界面中,該參數為一個[NSObject : AnyObject]?(字典)類型的數據。實現示例如下:
func respondsToBtn(sender:UIButton) {
// 2、post notification and send value
NSNotificationCenter.defaultCenter().postNotificationName("notification_name", object:nil, userInfo: ["text":self.textField.text!])
self.dismissViewControllerAnimated(true, completion:nil)
}
當用戶點擊返回按鈕時,發送通知,主界面接收到對應通知之后將會回調閉包,我們可在閉包中打印傳遞過來的數據,如下所示:
NSNotificationCenter.defaultCenter().addObserverForName("notification_name", object: nil, queue: NSOperationQueue.mainQueue()){(info)->Void in?
? ? ? print((info.userInfo!["text"])!)
}
到了這一步,我們已經基本實現通知傳值了,我們還需要最后一步,移除通知,通知的移除我們可在界面釋放的方法(析構方法)中去執行,如下所示:
deinit {
? ? ? // 3、remove notification
? ? ? NSNotificationCenter.defaultCenter().removeObserver(self)
}
2、協議傳值
協議傳值,主要用于代理模式。假設我們要實現從詳情界面傳值到主界面這一需求,首先,我們需要擬定一份協議,為了方便,我們可直接在詳情界面中擬定協議,如下所示:
importUIKit
//1、聲明協議
@objcprotocol DetailViewControllerDelegate {
optional func viewController(viewController: DetailViewController, dismissWithValue value: String)->Void
}
@objc關鍵字標識該協議為一個可選協議;optional關鍵字標識該協議方法對于協議的遵守者而言不是必須實現的。
聲明了協議之后,我們需要為詳情界面聲明一個代理屬性,如下所示:
class DetailViewController:UIViewController{
// 2、聲明協議屬性
? ? ? weakvardelegate: DetailViewControllerDelegate?
? ? ? ?overridefunc viewDidLoad() {
? ? ? ?}
}
代理屬性delegate值為實現了DetailViewControllerDelegate協議的任意對象,weak關鍵字主要為了防止循環引用導致對象無法釋放。
聲明了代理屬性之后,我們需要在處理界面跳轉(返回)的方法中處理協議傳值的邏輯了。首先我們需要判斷代理人是否存在,可通過可選綁定來操作,如果代理存在,則讓代理執行協議方法,并且將需要傳遞的信息通過參數傳遞給代理所在的界面,如下所示:
// MARK:- Events -
func respondsToBtn(sender:UIButton) {
? ? ? ? ?// 3、判斷代理是否存在,如果代理存在則讓代理執行協議方法并且將數據傳遞給代理
? ? ? ? if let delegate =self.delegate{
? ? ? ? ? ? ? ?delegate.viewController!(self, dismissWithValue:"123")
? ? ? ? }
? ? ? ? self.dismissViewControllerAnimated(true, completion:nil)
}
現在萬事具備,只缺“代理”了,切換到主界面中,在處理界面跳轉的方法中,我們將詳情界面的代理屬性設為主界面,如下所示:
// MARK:- Events -
func respondsToBtn(sender:UIButton) {
let detail_vc = DetailViewController()
// 設置代理
detail_vc.delegate = self
self.presentViewController(detail_vc, animated:true, completion:nil)
}
然后,實現協議方法,在協議方法中,我們可以直接獲取從詳情界面傳遞過來的value值。
// MARK:- DetailViewControllerDelegate -
func viewController(viewController: DetailViewController, dismissWithValuevalue: String) {
print(value)
}
3、閉包傳值
閉包主要用于回調,這里我們還是模擬從詳情界面傳值到主界面,首先我們需要在詳情界面為閉包取個別名,聲明一個閉包類型,如下所示:
// 1、聲明閉包類型
typealias Closure=(String?)->Void
其次在詳情界面控制器中,聲明閉包屬性:
// 2、聲明閉包屬性
varclosure: Closure!
接下來,我們需要為詳情界面聲明一個閉包回調的方法,用于在主界面中調用,并且為閉包屬性賦值,如下所示:
// MARK:- closure send values methods -
// 3、閉包傳值調用方法
func callBack(closure: Closure!) {
// 4、賦值閉包屬性
self.closure = closure
}
現在閉包屬性已經有值了,我們還需要在處理界面返回的方法中實現回調傳值的邏輯,同樣的,閉包類型為可選類型,我們可通過可選綁定判斷閉包屬性是否有值,如果有值,則通過閉包將需要傳遞到主界面的數據傳遞出去,代碼如下:
func respondsToBtn(sender:UIButton) {
//5、可選綁定:判斷closure屬性是否不為nil,如果不為nil,則通過closure將文本信息回調到調用closure方法所在的控制器中;
if let closure =self.closure {
? ? ? ? closure(self.textField.text)? ??
}
self.dismissViewControllerAnimated(true,completion:nil)}
現在詳情界面已經配置完畢,最后一步,我們在主界面推送到詳情界面的方法中,通過實例化的詳情界面對象,調用閉包回調方法,然后打印數據即可,該方法在詳情界面返回到主界面的時候會直接被調用,代碼如下:
// MARK:- Events -
func respondsToBtn(sender: UIButton) {
? ? let detail_vc = DetailViewController()
? ? detail_vc.callBack {?
? ? ? ? ? (value:String?)->Voidinprint(value!)
?? ? }
self.presentViewController(detail_vc, animated:true, completion: nil)}
Tips:
1、為閉包取別名,可在參數列表中將需要傳遞的參數寫成形參;
2、設置一個方法持有當前block;
3、在合適的地方進行調用類似于代理;
4、在創建該對象的地方進行閉包方面的調用;
4、單例傳值
單例,我們可以簡單理解為“一個類,一個實例”。因此,不管我們在什么地方創建單例,所得到的都是同一個實例,根據這一特點,我們可通過單例進行傳值,但是注意的是,單例傳值,在我們獲取單例值的時候首先必須保證單例確實有值,因此,我們可模擬通過單例實現從主界面傳值到詳情界面的場景。
要使用單例傳值,我們必須得創建單例,首先我們需要建立一個Swift文件,建立步驟:command + n -> iOS ->Swift File,為其取名為Singleton。然后我們在該文件中構造單例類,具體構造方式如下:
import Foundation
class Singleton{
// 單例屬性,用于傳值;
vartext: String!
privatestatic let _singleton = Singleton()
// 獲取單例實例方法
class func sharedInstance()->Singleton{
? ? ? return_singleton? ??
}
// 私有化init初始化方法,防止通過此方法創建對象
privateinit() {
?}
}
這里需要注意的是,我們可將需要傳遞的數據設置成單例的屬性,如上述代碼中的text屬性。在主界面為其賦值之后我們就可以在詳情界面訪問了。
接下來,我們在主界面跳轉到詳情界面的方法中,獲取單例實例并為其賦值,具體實現方式如下:
// MARK:- Events -
func respondsToBtn(sender: UIButton) {
//獲取單例實例
let singleton = Singleton.sharedInstance()
//為實例屬性賦值
singleton.text ="123456"
//界面推送
let detail_vc = DetailViewController()
self.presentViewController(detail_vc, animated:true, completion:nil)
}
現在單例屬性已經有值了,我們可在詳情界面的任意位置,獲取到單例實例,打印其屬性值:
//獲取單例實例
let singleton = Singleton.sharedInstance()
//打印實例屬性
print(singleton.text)
5、構造器傳值
構造器傳值,類似于OC的自定義初始化傳值,即在初始化某一個視圖控制器的時候,將需要傳遞的數據傳遞過去,因此,我們同樣可以模擬從主界面傳值到詳情界面。
首先,我們需要在詳情界面中,自定義構造器,具體實現方式如下:
class DetailViewController: UIViewController {
//析構器
deinit {
print(__FUNCTION__)
}
//自定義構造器
init(text: String) {
print(__FUNCTION__)
//打印參數
print(text)
//指定構造器必須調用它最近父類的指定構造器
super.init(nibName:nil, bundle:nil)
}
// init(coder aDecoder: NSCoder)方法是來自父類的指定構造器,因為這個構造器是required,必須要實現。但是因為我們已經重載了init(),定義了一個指定構造器,所以這個方法不會被繼承,需要要手動覆寫。
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
overridefunc viewDidLoad() {
super.viewDidLoad()
}
}
自定義構造器創建好之后,我們即可在主界面處理界面跳轉的方法中通過自定義構造器創建對象并傳值了,如下所示:
// MARK:- Events -func respondsToBtn(sender:UIButton) {
let detail_vc = DetailViewController(text:"123456")
self.presentViewController(detail_vc, animated:true, completion:nil)
}
6、屬性傳值
屬性傳值的方式特別簡單,其實現的基本思路,就是為創建對象并為其屬性賦值,同樣的,我們模擬從主界面傳值到詳情界面,首先我們在詳情界面聲明一個String類型的text屬性。
import UIKit
class DetailViewController:UIViewController{
? ? var text:String!
? ? override func viewDidLoad(){
? ? ? ? super.viewDidLoad()
? ? }
}
然后在處理主界面跳轉到詳情界面的方法中實例化詳情界面對象,并為其屬性賦值,如下所示:
func respondsToBtn(sender:UIButton) {
? ? let detail_vc = DetailViewController()
? ? detail_vc.text="123456"
? ? self.presentViewController(detail_vc, animated:true, completion:nil)
}
接下來,我們即可直接在詳情界面中的viewDidLoad()方法中打印屬性值了。
NSUserDefaults傳值
NSUserDefaults為系統單例,通過NSUserDefaults傳值和自定義單例傳值原理基本一致。對于NSUserDefaults傳值,首先在獲取值的地方,必須保證單例實例key對應的數據確實有值才行,同樣的,我們模擬從主界面傳值到詳情界面。
不管是存值還是取值,首先我們都需要獲取NSUserDefaults單例實例,系統為我們提供了standardUserDefaults()方法來獲取實例對象。為方便起見,這里我直接貼上代碼,以供各位參考。
主界面代碼:
import UIKit
class ViewController: UIViewController {
? ? override func viewDidLoad() {
? ? ? ? super.viewDidLoad()
? ? ? ? self.view.backgroundColor= UIColor.whiteColor()
? ? ? ? let btn = UIButton(type: UIButtonType.System)
? ? ? ? btn.bounds= CGRectMake(0,0,100,30)
? ? ? ? btn.center= self.view.centerbtn.setTitle("前往", forState: UIControlState.Normal)?
?? ? ? btn.titleLabel?.font= UIFont.boldSystemFontOfSize(20)
? ? ? ? btn.addTarget(self, action:"respondsToBtn:", forControlEvents: UIControlEvents.TouchUpInside) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? self.view.addSubview(btn)
? ? }
? ? func respondsToBtn(sender: UIButton) {
? ? ? ? // 獲取defaults單例實例
? ? ? ? let defaults = NSUserDefaults.standardUserDefaults()
? ? ? ? // 設值
? ? ? ? defaults.setValue("123456", forKey:"text")
? ? ? ? // 同步數據
? ? ? ? defaults.synchronize()
? ? ? ? let detail_vc = DetailViewController()
? ? ? ? self.presentViewController(detail_vc, animated: true, completion: nil)
? ? }
}
詳情界面代碼:
import UIKit
class DetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 獲取defaults單例實例
let defaults = NSUserDefaults.standardUserDefaults()
// 獲取單例實例key對應的value值
let value= defaults.valueForKey("text")
// 打印value值print(value!)
}
}
順便提一下,通過NSUserDefaults傳值,我們不僅僅只局限于傳遞字符串類型的數據,我們同時可傳遞集合類型(字典、數組、集)或者基本數據類型的數據。編譯器也為我們提供了各種方法來實現不同的需求,如下所示:
設值方法
// 1、對象類型
public func setValue(value: AnyObject?, forKey key: String)
// 2、基本數據類型
public func setInteger(value: Int, forKey defaultName: String)
public func setFloat(value: Float, forKey defaultName: String)
public func setDouble(value: Double, forKey defaultName: String)
public func setBool(value: Bool, forKey defaultName: String)
取值方法
// 1、對象類型
publicfunc valueForKey(key:String)->AnyObject?
// 2、基本數據類型
public func integerForKey(defaultName:String)->Int
public func floatForKey(defaultName:String)->Float
public func doubleForKey(defaultName:String)->Double
public func boolForKey(defaultName:String)->Bool
Tips:無論是系統單例還是自定義單例,只要單例對應的key或者屬性確實有值,我們就可以在整個項目的任意文件中訪問對應數據。