iOS 快速從OC過渡到Swift,由理論到實戰-OC和Swift混編
接上篇文章:iOS 快速從OC過渡到Swift,由理論到實戰-Swift基礎
OC和Swift混編
a. Swift 和 OC 的對映關係
Swift 相容來大部分 OC,當然還有一些 Swift 不能夠使用的,例如 OC 中的預處理指令,即巨集定義不可使用,雖然在目前4.2版本下,已經開始支援了少量的巨集,如
#if DEBUG
#else
#endif
這種簡單的預處理指令。Swift 中推薦使用常量定義,定義一些全域性的常量例項、全域性方法來充當 巨集 的作用。
我們直到,Swift 中的一些自定義基類可以是不繼承自 NSObject,這是 OC 呼叫這類的的時刻,就需要使用關鍵自 @objc 來標記,這樣就可以正常使用來。
Swift中還有很多小細節部分,待大家在進一步學習過程中慢慢體會。
b. Objc 呼叫 Swift
由於國內大部分還都是 OC 程式設計環境,所以通常是先學習 OC 下呼叫 Swift。接下來我演示以下 OC 下配置橋接檔案,以及 Objc 和 Swift 的相互呼叫。
當我們在專案中第一次新增不同語言的檔案時,Xcode就會詢問你是否建立橋接檔案,你只需要點選”YES”,Xcode就可以幫你完成橋接配置(注意:第二次不會提示你,即使你刪除來橋接檔案)。
這份橋接檔案是 Swift 呼叫 OC 檔案的時,匯入 OC 標頭檔案使用的。在橋接檔案中你可看到以下內容。
// Use this file to import your target's public headers that you would like to expose to Swift.
當然你也可以手動建立橋接檔案,自己配置環境。
我們可以建立”專案名稱-Bridging-Header”一個 .h 檔案作為橋接檔案,然後為專案配置此檔案的路徑。
此檔案是存放 Swift 使用的 OC 標頭檔案,此小節先不管它。
在 OC 呼叫 Swift 情況下,是通過Swift生成的一個頭檔案實現的,這個標頭檔案是編譯器自動完成的,它會將你在專案是到的 Swift 檔案自動對映稱 OC 語法,其他並不需要開發關注,該檔名稱為 “專案名稱-Swift”,你可以在 Build Settings 裡搜尋 Swift 可以看到:
這裡還可以配置其他 Swift 的選項,如語言版本等。
接下來我們建立一個名為 “AViewController.swift” 的控制器,我們現在要在 OC 檔案使用到該檔案,首先,我們匯入”專案名稱-Swift.h”的檔案,不會提示,需要你手動寫入(可以按住Command點選標頭檔案檢視)。
在 OC 的 “ViewController.h” 檔案中,我們進行跳轉控制器,這個控制器的使用和使用 OC的類沒有任何卻別,系統的”專案名稱-Swift.h”檔案已經幫我完成了對映。
#import "ViewController.h"
#import "TestDemo-Swift.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"OC頁面";
self.view.backgroundColor = [UIColor whiteColor];
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
AViewController *ctrl = [AViewController new];
[self.navigationController pushViewController:ctrl animated:YES];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
c. Swift 呼叫 Objc
前面提到了 “TestDemo-Bridging-Header.h” 檔案,這個檔案中儲存的是用於供給 Swift 使用的 OC 標頭檔案。
我們將 OC 控制器檔案 “ViewController.h” 匯入該檔案,這樣我們在 Swift 檔案中就可以使用該檔案。
import UIKit
class AViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Swift頁面";
self.view.backgroundColor = UIColor.red
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
let ctrl = ViewController()
self.navigationController?.pushViewController(ctrl, animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
上面介紹了在 OC 工程中建立 Swift 檔案,其實在 Swift 工程中建立 OC 檔案是類似的,大家可以嘗試一下。
Swift中的’巨集’和工具類
通過前面的學習,我們已經對 Swift 的語法,和 OC 進行混編等知識有了一定的瞭解,下面演示一些在專案中可能使用到的工具類。
我們知道,在目前的Swift中,還不能夠呼叫OC中的巨集定義,這些東西勢必需要我們重新寫過。在Swift中,所對應OC的巨集,只不過是一些全域性常量或者方法。下面列舉一些我的在OC專案中對映多來的”巨集”。
// MARK: -------------------------- 適配 --------------------------
/// 螢幕寬度
let kScreenHeight = UIScreen.main.bounds.height
/// 螢幕高度
let kScreenWidth = UIScreen.main.bounds.width
/// 狀態列高度
let kStatusBarHeight = UIApplication.shared.statusBarFrame.size.height
/// 導航欄高度
let kNavigationbarHeight = (kStatusBarHeight+44.0)
/// Tab高度
let kTabBarHeight = kStatusBarHeight > 20 ? 83 : 49
/// 螢幕比率
let kScreenWidthRatio = (kScreenWidth/375.0)
/// 調整
func AdaptedValue(x:Float) -> Float {
return Float(CGFloat(x) * kScreenWidthRatio)
}
func ShiPei(x:Float) -> Float{
return AdaptedValue(x: x)
}
/// 調整字型大小
func AdaptedFontSizeValue(x:Float) -> Float {
return (x) * Float(kScreenWidthRatio)
}
// MARK: -------------------------- 顏色 --------------------------
// MARK: 請參考 UIColor_Extentsion.swift 檔案
// MARK: -------------------------- 偏好 --------------------------
func SaveInfoForKey(_ value:String ,_ Key:String) {
UserDefaults.standard.set(value, forKey: Key)
UserDefaults.standard.synchronize()
}
func GetInfoForKey(_ Key:String) -> Any! {
return UserDefaults.standard.object(forKey: Key)
}
func RemoveObjectForKey(_ Key:String){
UserDefaults.standard.removeObject(forKey: Key)
UserDefaults.standard.synchronize()
}
// MARK: -------------------------- 輸出 --------------------------
// 帶方法名、行數
func printLog(_ message:T,method:String = #function,line:Int = #line){
print("-[method:\(method)] " + "[line:\(line)] " + "\(message)")
}
// 只在Debug下輸出,為了給習慣OC輸出寫法的同事
func DLog(_ format: String, method:String = #function,line:Int = #line,_ args: CVarArg...){
// print("-[method:\(method)] " + "[line:\(line)] ", separator: "", terminator: "")
#if DEBUG
let va_list = getVaList(args)
NSLogv(format, va_list)
#else
#endif
}
// 只在Debug下輸出,為了為習慣PHP輸出寫法的同事
func echo(_ format: String,_ args: CVarArg...) {
#if DEBUG
let va_list = getVaList(args)
NSLogv(format, va_list)
#else
#endif
}
使用【擴充套件】功能建立一些工具
顏色類
extension UIColor {
// RGB顏色
static func rgba(_ r:CGFloat,_ g:CGFloat,_ b:CGFloat,_ a:CGFloat) -> UIColor {
return UIColor.init(red: (r)/255.0, green: (g)/255.0, blue: (b)/255.0, alpha: a)
}
static func rgb(_ r:CGFloat,_ g:CGFloat,_ b:CGFloat) -> UIColor {
return rgba((r), (g), (b), 1)
}
/// 十六進位制的色值 例如:0xfff000
static func hex(_ value:Int) -> UIColor{
return rgb(CGFloat((value & 0xFF0000) >> 16),
CGFloat((value & 0x00FF00) >> 8),
CGFloat((value & 0x0000FF)))
}
/// 十六進位制的色值,例如:“#FF0000”,“0xFF0000”
static func hexString(_ value:String) -> UIColor{
var cString:String = value.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
cString = cString.lowercased()
if cString.hasPrefix("#") {
cString = cString.substring(from: cString.index(after: cString.startIndex))
}
if cString.hasPrefix("0x") {
cString = cString.substring(from: cString.index(cString.startIndex, offsetBy: 2))
}
if cString.lengthOfBytes(using: String.Encoding.utf8) != 6 {
return UIColor.clear
}
var startIndex = cString.startIndex
var endIndex = cString.index(startIndex, offsetBy: 2)
let rStr = cString[startIndex..<endIndex]
startIndex = cString.index(startIndex, offsetBy: 2)
endIndex = cString.index(startIndex, offsetBy: 2)
let gStr = cString[startIndex..<endIndex]
startIndex = cString.index(startIndex, offsetBy: 2)
endIndex = cString.index(startIndex, offsetBy: 2)
let bStr = cString[startIndex..<endIndex]
var r :UInt32 = 0x0;
var g :UInt32 = 0x0;
var b :UInt32 = 0x0;
Scanner.init(string: String(rStr)).scanHexInt32(&r);
Scanner.init(string: String(gStr)).scanHexInt32(&g);
Scanner.init(string: String(bStr)).scanHexInt32(&b);
return rgb(CGFloat(r), CGFloat(g), CGFloat(b))
}
/// 隨機色
static var rand: UIColor {
return rgb(CGFloat(arc4random_uniform(255)), CGFloat(arc4random_uniform(255)), CGFloat(arc4random_uniform(255)))
}
}
字元轉數字類
extension String {
/// 將字串轉換為合法的數字字元
///
/// - Returns: String型別
func toPureNumber() -> String {
let characters:String = "0123456789."
var originString:String = ""
for c in self {
if characters.contains(c){
if ".".contains(c) && originString.contains(c){}
else{
originString.append(c)
}
}
}
return originString
}
/// 將字串轉換為 Double 型別的數字
///
/// - Returns: Double型別
func toDouble() -> Double {
return Double(self.toPureNumber())!
}
/// 將字串轉換為 Float 型別的數字
///
/// - Returns: Float型別
func toFloat() -> Float {
return Float(self.toDouble())
}
/// 將字串轉換為 Int 型別的數字
///
/// - Returns: Int型別
func toInt() -> Int {
return Int(self.toDouble())
}
// 保留舊版本的字元轉換為數字型別,例:"123".floatValue
var pureNumber: String {
return self.toPureNumber()
}
var doubleValue: Double {
return self.toDouble()
}
var floatValue: Float {
return self.toFloat()
}
var intValue: Int {
return self.toInt()
}
}
以上介紹了幾種工具類的建立方式,相信大家進過基礎一節過後,都能看懂。
實戰篇
在這一節中,我們利用之前的測試專案簡單的演示Swift下如何搭建頁面、和OC進行資料傳遞,其中會涉及到自定義資料模型、延遲載入、型別轉換、協議、協議代理模式、弱引用、閉包、閉包傳值、檢測釋放等等,希望能都幫助到第一次接觸 Swift 的小白們。
Demo中,簡單搭建一個Swift頁面,頁面中展示一個列表,給列表Cell有標題,自標題和一張圖片,使用者點選某個cell時,將資料回傳到上一個OC頁面。
a. 定義Model層
首先我們需要要一個自定義模型,以及一個用來獲取、處理資料的View-Model。
首先是自定義模型:
import UIKit
// 列表資料來源
class AVTableModel: NSObject {
var title: String?
var subTitle: String?
var image: String?
}
View-Model:
import UIKit
class AVTableManager: NSObject {
// 定義資料請求,使用閉包回撥
class func requestData(response:@escaping (_ flag : Bool,_ resArray : [AVTableModel]?) -> Void) {
// 模擬資料
var resList = [AVTableModel]()
for i in 0...30 {
// 使用自定義模型
let avtm:AVTableModel = AVTableModel()
avtm.title = "\(i) row"
avtm.subTitle = [
"月光推開窗親吻臉頰,沁得芬芳輕叩門,斂去湖中婆娑影,拈起肩上落梨花",
"屋檐下的風輕輕拂過了衣角,弄皺了桌上的畫卷,月影疏疏,落花朵朵,不經意間,看成了風景",
"遠處的煙穿過水路,哼著不知名的小調,踏碎了一方的圓月",
"誰家的酒釀醉了空氣中潮溼的時光,睜眼瞬間,藏進了樓閣"
][i%4]
avtm.image = ["0.jpg","1.jpg","2.jpg"][i%3]
resList.append(avtm)
}
// 回撥資料和當前狀態
response(true, resList)
}
}
b. 控制器
控制器需要完成UI的搭建,因此需要一個列表,完成相應的列表代理,再者就是定義資料,完成頁面互動邏輯。
Swift 中列表是使用和 OC 非常型別,僅僅是方法的實現和呼叫方面是鏈式呼叫。
import UIKit
class AViewController: UIViewController,UITableViewDelegate,UITableViewDataSource { // 實現列表的代理
// 資料來源,延遲屬性
lazy var dataArray = [AVTableModel]()
// 列表檢視
@IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Swift頁面";
// 配置UI
self.setUpUI()
// 獲取資料
self.getData() // 獲取資料
}
// 配置UI
func setUpUI() {
self.table.tableFooterView = UIView()
}
// 獲取資料
func getData() {
// 這裡因為是類方法,不會產生迴圈引用
AVTableManager.requestData { (flag, resList) in
if flag {
self.dataArray = resList!
self.table.reloadData()
}
}
}
// tableView的代理
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 建立cell,並返回給tableView
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60.5
}
// 檢測釋放
deinit {
print("\(self.classForCoder) 釋放了。。。")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
c. 檢視層
我這裡使用的是 xib,使用方式和 OC 下並無多少差異,將一些必要的控制元件拉到類中做入口。
import UIKit
class AVTableViewCell: UITableViewCell {
@IBOutlet weak var titleLabel: UILabel! // 標題
@IBOutlet weak var subTitleLabel: UILabel! // 子標題
@IBOutlet weak var imgView: UIImageView! // 圖片
}
在控制器中完成列表cell的註冊,以及cell的賦值
import UIKit
class AViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
// 資料來源,延遲屬性
lazy var dataArray = [AVTableModel]()
// 列表檢視
@IBOutlet weak var table: UITableView!
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Swift頁面";
// 配置UI
self.setUpUI()
// 獲取資料
self.getData() // 獲取資料
}
// 配置UI
func setUpUI() {
self.table.tableFooterView = UIView()
// 註冊cell
self.table .register(UINib.init(nibName: "AVTableViewCell", bundle: nil), forCellReuseIdentifier: cellId)
}
// 獲取資料
func getData() {
// 這裡因為是類方法,不會產生迴圈引用
AVTableManager.requestData { (flag, resList) in
if flag {
self.dataArray = resList!
self.table.reloadData()
}
}
}
// tableView的代理
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 將UITableViewCell向下轉換為AVTableViewCell
let cell: AVTableViewCell = table.dequeueReusableCell(withIdentifier: cellId) as! AVTableViewCell
let model = self.dataArray[indexPath.row]
// 將資料賦值給cell
cell.titleLabel.text = model.title
cell.subTitleLabel.text = model.subTitle
cell.imgView.image = UIImage.init(named: model.image!)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60.5
}
deinit {
print("\(self.classForCoder) 釋放了。。。")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
d. 將資料回傳到 OC 頁面
這裡演示代理和閉包兩種方式
首先是代理,和 OC 一樣,我們需要先定義一個協議用來約束遵循者完成該協議方法。
import Foundation
@objc protocol AViewControllerDelegate {
// 預設是必須實現的,這裡將其定義為可選型別
@objc optional func aViewCtrlData(model: AVTableModel) -> Void
}
那麼在控制器中,代理和閉包的宣告就可以想下面那樣
// 代理,將資料傳遞迴其他類,可選型別
weak var delegate: AViewControllerDelegate?
// 閉包,將第一條資料傳遞迴其他類, 該閉包和代理一樣是可選型別
var returnModelBlock: ((_ model: AVTableModel) ->Void)?
我們在使用者點選事件中將對應資料回傳到 OC 頁面中去
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// 我們將前十項的資料使用代理方法回傳,剩餘的資料使用閉包方式回傳
if indexPath.row > 10 {
// 將資料通過代理回傳給 OC 頁面
let model = self.dataArray[indexPath.row]
self.delegate?.aViewCtrlData!(model: model)
}
else {
// 將資料回傳給其他類
if returnModelBlock != nil {
returnModelBlock!(self.dataArray.first!)
}
}
// 返回
self.navigationController?.popViewController(animated: true)
}
f. 在 OC 頁面中,使用 Swift 頁面中的block和代理和平常沒有什麼區別
AViewController *ctrl = [AViewController new];
ctrl.delegate = self;
// 這裡並沒有迴圈引用,因為self並沒有持有ctrl,ctrl僅是被self.navigationController所持有
ctrl.returnModelBlock = ^(AVTableModel * model) {
[self aViewCtrlDataWithModel:model];
};
[self.navigationController pushViewController:ctrl animated:YES];
演示:
注:由於 OC 和 Swift 混編是由橋接檔案實現橋接的,因此並不是你寫完一個public屬性在OC中就能夠使用的,在使用前,你應該手動 command + B 進行編譯完成橋接檔案的內容。你可以使用 command + 點選TestDemo-Swift.h檔案 進入橋接檔案檢視橋接內容。
宣告
上述部分內容參考自:
Swift 4.0 教程 – Swift程式設計
Kenshin Cui’s Blog
作者:LOLITA0164