一.前言
隨著Swift的逐漸完善, 越來越多的開發(fā)者轉型到Swift放棄OC, 其中一大部分是2017年后的培訓機構, 一部分是OC開發(fā)者迫于公司項目轉型的, 這樣一來國內iOS市場不僅被跨平臺蠶食又被Swift蠶食, 老的OC程序員找工作越來越困難, 作為一名 OC擁護者我想我也有寫一點東西的必要了, 本文主要講的就是OC轉型Swift需要注意的地方, 我希望把它作為長期項目寫下去, 需要用到的時候就直接查找, 不至于手忙腳亂, 下面就跟著我們的文章一起來看吧.
溫馨提示: 文章內容是根據自己的理解試出來的, 有些來自于百度, 請酌情閱讀, 如有錯誤請?zhí)嵝盐壹皶r修改.
二.MVC
(1) Model
我們可以看到Swift中定義屬性, 直接干掉了屬性修飾符, 與之代替的是可選類型和泛型
OC
@interface Student : NSObject
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSArray *arr;
@property (strong, nonatomic) NSDictionary *dic;
@end
溫馨提示: 請不要跟我辯論字符串是否必須用copy謝謝
Swift
class Student {
var name: String?
var arr: Array<Any>?
var dic: Dictionary<String, Any>?
}
getter / setter
swift與oc不同的是 swift不會自動生成內部成員變量 如果想儲存setter需要第二個變量 如果寫set就必須寫get 如果只寫get那就是只讀, 而oc只讀使用readonly修飾
class Cat {
private var _name: String?
var name: String? {
set {
_name = newValue;
}
get {
return _name;
}
}
}
willSet / didSet
swift為了賦值方便提供了只寫set的方法, 它相當于一個鉤子, 雖然不能改變set的值, 但是能在賦值前和賦值后去做一些事情
class Cat {
var name: String? {
willSet {
print("賦值前 " + (self.name ?? ""))
}
didSet {
print("賦值后 " + (self.name ?? ""))
}
}
}
(2) View
這個模塊感覺沒什么好說的 以后想起來再說吧 上面剛講完willset和didset 下面就來簡單的講一下如何使用
cell中賦值的方法
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as! CustomTableViewCell
cell.model = self.arr?[indexPath.row] as? CustomModel;
cell.selectionStyle = .none;
return cell
}
class CustomTableViewCell: UITableViewCell {
var model: CustomModel? {
didSet {
self.titleLabel.text = self.model?.name;
}
}
@IBOutlet weak var titleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
(3) Controller
感覺這個也沒啥好說的, 說一下dealloc吧, 我覺得視圖控制器最重要的就是釋放, 當然你如果覺得不重要就當我沒說 - -
OC
- (void)dealloc {
NSLog(@"控制器釋放---%@", NSStringFromClass([self class]));
}
Swift
可以看到swift使用deinit取代了dealloc
deinit {
print("控制器釋放---\(type(of: self))")
}
三.框架
數據解析
下面來介紹一下 json字符串轉字典和字典轉模型
字符串轉字典
let jsonString = """
{"name": "張三"}
"""
do {
let object = try JSONSerialization.jsonObject(with: jsonString.data(using: .utf8) ?? Data(), options: .allowFragments)
print(object)
} catch {
print(error)
}
或
// 處理可能沒有
let object = try? JSONSerialization.jsonObject(with: jsonString.data(using: .utf8) ?? Data(), options: .allowFragments)
print(object ?? [])
// 處理一定有
let object = try! JSONSerialization.jsonObject(with: jsonString.data(using: .utf8) ?? Data(), options: .allowFragments)
print(object)
字典轉model
pod 'KakaJSON'
https://github.com/kakaopensource/KakaJSON
class Cat: Convertible {
required init() {}
var name: String?
}
let catJson: [String: Any] = [
"name": "huahua"
]
let cat = catJson.kj.model(Cat.self)
數組轉model數組
let catJsonArray: [Any] = [
["name": "huahua"],
["name": "zhangsan"],
["name": "lisi"]
]
let catArray = catJsonArray.kj.modelArray(Cat.self)
model中包含其他對象
class Person: Convertible {
required init() {}
var name: String?
var cat: Cat?
}
let personJson: [String: Any] = [
"name": "huahua",
"cat": ["name": "huahua"]
]
let person = personJson.kj.model(Person.self)
model數組屬性中包含其他對象
class Person: Convertible {
required init() {}
var name: String?
var cat: Cat?
var cats: [Cat]?
}
let personJson: [String: Any] = [
"name": "huahua",
"cat": ["name": "huahua"],
"cats": [
["name": "huahua"],
["name": "zhangsan"],
["name": "lisi"]
]
]
let person = personJson.kj.model(Person.self)
model轉json
let personDict = JSONObject(from: person)
model屬性替換
有時候定義的屬性不一定和json完全對上 為了能夠進行解析 需要進行屬性替換
比如這里有cat 但是定義在model中的是cat2 需要把cat解析到cat2上
這里有一個口訣(順序)
沒有cat2就找cat
let personJson: [String: Any] = [
"name": "huahua",
"cat": ["name": "huahua"],
"cats": [
["name": "huahua"],
["name": "zhangsan"],
["name": "lisi"]
]
]
class Person: Convertible {
required init() {}
var name: String?
var cat2: Cat?
var cats: [Cat]?
func kj_modelKey(from property: Property) -> ModelPropertyKey {
switch property.name {
case "cat2": return "cat"
default: return property.name
}
}
}
json屬性替換
有時候轉化成json的時候需要替換某些key
拿上面的舉例子 轉回json的時候應該是cat2 如果這時你想轉回原json 也就是cat
那么需要進行json屬性替換
func kj_JSONKey(from property: Property) -> JSONPropertyKey {
switch property.name {
case "cat2": return "cat"
default: return property.name
}
}
網絡請求
pod 'Alamofire'
https://github.com/Alamofire/Alamofire
import Alamofire
// get 默認
AF.request("http://localhost:8080/api/v1/hello").response { response in
debugPrint(response)
}
// post
AF.request("http://localhost:8080/api/v1/hello2", method: .post).response { response in
debugPrint(response)
}
json解析成字典
AF.request("http://localhost:8080/api/v1/hello").response { response in
let json = try? JSONSerialization.jsonObject(with: response.data ?? Data(), options: .allowFragments) as? Dictionary<String, Any>
print(json ?? Dictionary())
}
json解析成對象
import Foundation
import KakaJSON
class DataModel: Convertible {
required init() {}
var success: Bool?
var result: Dictionary<String, Any>?
var code: Int?
}
AF.request("http://localhost:8080/api/v1/hello3").response { response in
let model = response.data?.kj.model(DataModel.self)
debugPrint(model?.result ?? Dictionary())
}
圖片展示
沒啥好說的
pod 'Kingfisher'
https://github.com/onevcat/Kingfisher
import Kingfisher
self.coverImageView?.kf.setImage(with: URL(string: "https://img2.baidu.com/it/u=2037072674,1804134981&fm=26&fmt=auto&gp=0.jpg"))
布局框架
基本就跟Masonry一樣
pod 'SnapKit'
https://github.com/SnapKit/SnapKit
完全鋪滿
import SnapKit
let view = UIView()
view.backgroundColor = .red
self.view.addSubview(view)
view.snp.makeConstraints { (make) in
make.edges.equalTo(self.view)
}

安全區(qū)域鋪滿
view.snp.makeConstraints { (make) in
if #available(iOS 11, *) {
make.edges.equalTo(self.view.safeAreaLayoutGuide)
} else {
make.top.equalTo(self.topLayoutGuide.snp.top)
make.bottom.equalTo(self.bottomLayoutGuide.snp.bottom)
make.left.equalTo(self.view)
make.right.equalTo(self.view)
}
}
// 其中make.edges.equalTo(self.view.safeAreaLayoutGuide)可以拆分成
self.view.safeAreaLayoutGuide.snp.top
self.view.safeAreaLayoutGuide.snp.bottom
self.view.safeAreaLayoutGuide.snp.left
self.view.safeAreaLayoutGuide.snp.right
看到區(qū)別了么 導航欄上面的紅色不見了 這就是安全區(qū)域

Toast
pod 'Toast-Swift'
https://github.com/scalessec/Toast-Swift
基本使用
self.view.makeToast("123", duration: 3, position: .center)
全局設置樣式
主要是用 ToastManager 的單例實現(xiàn)的
var toastStyle = ToastStyle()
toastStyle.messageColor = .blue
ToastManager.shared.style = toastStyle
ToastManager.shared.isTapToDismissEnabled = false
ToastManager.shared.isTapToDismissEnabled = false
四.混編
在swift開發(fā)中難免會用到oc的庫, 那么要怎么使用呢
導入庫
如果是庫直接就可以使用
import SDWebImage
self.coverImageView?.sd_setImage(with: URL(string: "https://img2.baidu.com/it/u=2037072674,1804134981&fm=26&fmt=auto&gp=0.jpg"), completed: nil)
自定義類
如果是自定義類 就需要創(chuàng)建橋接文件了


Swift中調用OC
1.在橋接文件中引入OC類

2.直接在swift中使用
let person = Person()
person.name = "123"
print(person)
OC中調用Swift
1.導入swift頭文件 規(guī)則是#import "項目名-Swift.h", 我的項目叫SwiftProject
#import <Foundation/Foundation.h>
#import "SwiftProject-Swift.h"
@interface Abc : NSObject
@end
2.使用
雖然Student是Swift類, 但使用方法與OC無異, 唯一需要注意的是Swift類的成員變量OC不能直接使用, 需要@objc, 否則OC無法訪問, 并且一定要繼承一個基類, 否則OC無法識別
class Student: NSObject {
@objc var name: String?
}
@implementation Abc
- (instancetype)init
{
self = [super init];
if (self) {
Student *stu = [[Student alloc] init];
stu.name = @"123";
NSLog(@"%@", stu.name);
}
return self;
}
@end
但有時候會出現(xiàn)這個問題
Cycle inside SwiftProject; building could produce unreliable results. This usually can be resolved by moving the target's Headers build phase before Compile Sources.
Cycle details:
→ Target 'SwiftProject': CodeSign /Users/macmini/Library/Developer/Xcode/DerivedData/SwiftProject-djnxjwwjwnrhjqbulwazwcvkbjnh/Build/Products/Debug-iphonesimulator/SwiftProject.app
Target 'SwiftProject' has process command with output '/Users/macmini/Library/Developer/Xcode/DerivedData/SwiftProject-djnxjwwjwnrhjqbulwazwcvkbjnh/Build/Products/Debug-iphonesimulator/SwiftProject.app/Info.plist'
Target 'SwiftProject' has compile command with input '/Users/macmini/demo/SwiftProject/SwiftProject/Assets.xcassets'
Target 'SwiftProject' has compile command with input '/Users/macmini/demo/SwiftProject/SwiftProject/Abc.m'
Target 'SwiftProject' has compile command for Swift source files
Target 'SwiftProject': Ditto /Users/macmini/Library/Developer/Xcode/DerivedData/SwiftProject-djnxjwwjwnrhjqbulwazwcvkbjnh/Build/Intermediates.noindex/SwiftProject.build/Debug-iphonesimulator/SwiftProject.build/DerivedSources/SwiftProject-Swift.h /Users/macmini/Library/Developer/Xcode/DerivedData/SwiftProject-djnxjwwjwnrhjqbulwazwcvkbjnh/Build/Intermediates.noindex/SwiftProject.build/Debug-iphonesimulator/SwiftProject.build/Objects-normal/x86_64/SwiftProject-Swift.h
Target 'SwiftProject' has compile command for Swift source files
原因是可能是swift和oc頭文件循環(huán)引用了, 解決方案是去掉bridge中的oc類 或者 去掉類中的swift頭文件

個人總結: 即一個開放給swift的OC類不能再調用swift的類
五.文檔
數據類型
OC
typedef NS_ENUM(NSUInteger, StudentGender) {
StudentGenderBoy,
StudentGenderGirl
};
@interface Student : NSObject
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSArray *arr;
@property (strong, nonatomic) NSDictionary *dic;
@property (strong, nonatomic) NSNumber *number;
@property (assign, nonatomic) NSInteger integer;
@property (assign, nonatomic) float testFloat;
@property (assign, nonatomic) double testDouble;
@property (assign, nonatomic) BOOL selected;
@property (assign, nonatomic) StudentGender gender;
@end
Swift
enum StudentGender {
case StudentGenderBoy
case StudentGenderGirl
}
class Student: NSObject {
var name: String?
var arr: Array<Any>?
var dic: Dictionary<String, Any>?
var number: Int?
var testFloat: Float?
var testDouble: Double?
var selected: Bool?
var gender: StudentGender?
}
Block
之所以放在這里是因為Block是與OC區(qū)別最大的東西, 下面就簡單來說一說
創(chuàng)建
let testBlock = {() -> Void in
print("無參數無返回值")
}
testBlock()
// 或 下面這種是匿名立即執(zhí)行的簡便寫法
// 無參數無返回值
{() -> Void in
print("無參數無返回值")
}()
// 有參數無返回值
{(a: String) -> Void in
print(a)
print("有參數無返回值")
}("123")
// 有參數有返回值
{(a: String) -> String in
print("有參數有返回值")
return a;
}("123")
回調
block回調在oc中非常常見, 那么在swift中要怎么使用呢
func say(callback: ()->Void) -> Void {
callback()
}
func say(name: String, callback: ()->Void) -> Void {
callback()
}
func say(name: String, callback: ()->Void, callback2: ()->Void) -> Void {
callback()
callback2()
}
func say(name: String, callback: (_ a: String)->Void) -> Void {
callback(name)
}
使用也很簡單 自己領悟吧
let stu = Student()
stu.say {
}
stu.say(name: "456") {
}
stu.say(name: "4564") {
} callback2: {
}
stu.say(name: "789") { (a) in
print(a)
}
解決循環(huán)引用
先制造一個
var callback: (() -> Void)?
var stu: Student?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.stu = Student()
self.stu?.say {
self.navigationController?.title = "123"
}
}
點擊返回發(fā)現(xiàn)控制器不釋放了 stu和self循環(huán)引用了 怎么解決呢
self.stu?.say { [weak self] in
self?.navigationController?.title = "123"
}
可以看到 swift 是用了 [weak self]解決
六.SwiftUI
在老項目中引用
let vc = UIHostingController(rootView: SwiftUIView())
// push
self.navigationController?.pushViewController(vc, animated: true)
未完待續(xù), 持續(xù)更新中