一、OC 到 Swift 基础差异
- 1.1、提示符:MARK、TODO、FIXME、 #warning("")
- <1>、
// MARK:
类似于OC中的#pragma mark
// MARK: 测试 func test() -> () { }
<2>、// MARK: -
类似于OC中的 #pragma mark -
// MARK: - 上面的方法 // MARK: 方法一 func test() -> () { } // MARK: - 下面的方法 // MARK: 方法一 func test3() -> () { }
<3>、// TODO:
用于标记未完成的任务
func test2() -> () { // TODO: 未完成的任务 }
<4>、// FIXME:
用于标记待修复的问题
func test3() -> () { // FIXME: 待修复 }
<5>、#warning("") 警告信息
1.2、条件编译
// 操作系统:macOS\iOS\tvOS\watchOS\Linux\Android\Windows\FreeBSD #if os(macOS) || os(iOS) // CPU架构:i386\x86_64\arm\arm64 #elseif arch(x86_64) || arch(arm64) // swift版本 #elseif swift(<5) && swift(>=3) // 模拟器 #elseif targetEnvironment(simulator) // 可以导入某模块 #elseif canImport(Foundation) #else #endif
- 自定义条件编译
- 系统默认的是
DEBUG
,但是我们可以改名字,根据自己的需要了
// debug模式 #if DEBUG // release模式 #else #endif #if TEST print("test") #endif #if OTHER print("other") #endif
- 1.3、DUBUG 和 Release 模式下的打印
- OC 中
#ifdef DEBUG #define JKLog(...) NSLog(@"%s 第%d行: %@\n\n",__func__,__LINE__,[NSString stringWithFormat:__VA_ARGS__]) #else #define JKLog(...) #endif
- Swift 中
/// 自定义打印 /// - Parameter msg: 打印的内容 /// - Parameter file: 文件路径 /// - Parameter line: 打印内容所在的函数 /// - Parameter fn: 打印内容的函数名 func JKLog<T>(_ msg: T, file: NSString = #file, line:Int = #line, fn: String = #function) { #if DEBUG let prefix = "------\n当前文件是:\(file.lastPathComponent)\n第 \(line) 行\n函数名:\(fn)\n打印内容:msg\n------" print(prefix) #endif }
- 1.4、系统版本检测
- 对于iOS平台,只在iOS10及以上版本执行
- 对于macOS平台,只在macOS 10.12及以上版本执行
- 最后的*表示在其他所有平台都执行
if #available(iOS 10, macOS 10.12, *) { }
- 1.5、API 可用性说明
@available(iOS 10, macOS 10.15, *) class Person {} struct Student { // 方法更名 @available(*, unavailable, renamed: "study") func study_() {} func study() {} // 在某个系统下废弃了 @available(iOS, deprecated: 11) @available(macOS, deprecated: 10.12) func run() {} }
- 方法更名:
@available(*, unavailable, renamed: "study")
- 更多的说明方法可以 参考官方
- 1.6、拓展小技巧
- 当我们在写一个函数的时候,可能里面不知道些什么,又不想让它报错,我们可以在作用域内写上:
fatalError()
func testDo() -> Int { fatalError() }
二、Swift 项目说明
- 2.1、iOS 程序的入口
- 在AppDelegate上面默认有个
@UIApplicationMain
标记,这表示:编译器自动生成入口代码(main函数代码),自动设置AppDelegate
为APP的代理 - 也可以删掉
@UIApplicationMain
,自定义入口代码:新建一个main.swift
文件,如下:JKUIApplication
使我们自定义的入口
import UIKit class JKUIApplication: UIApplication {} UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, NSStringFromClass(JKUIApplication.self), NSStringFromClass(UIApplication.self))
- 2.2、Swift 调用 OC
- 先建一个桥接文件:直接见一个OC类,Xcode会直接提示我们创建一个桥接文件,文件名格式默认为:
{targetName}-Bridging-Header.h
- 在
{targetName}-Bridging-Header.h
文件中#import OC需要暴露给Swift的内容,如下
- 自定义类 Animal, 其中 .h 和 .m文件内容如下
// .h文件 #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN int sum(int a, int b); @interface Animal : NSObject @property (nonatomic, assign) NSInteger age; @property (nonatomic, copy) NSString *name; - (instancetype)initWithAge:(NSInteger)age name:(NSString *)name; + (instancetype)personWithAge:(NSInteger)age name:(NSString *)name; - (void)run; + (void)run; - (void)eat:(NSString *)food other:(NSString *)other; + (void)eat:(NSString *)food other:(NSString *)other; @end NS_ASSUME_NONNULL_END // .m 文件 #import "Animal.h" @implementation Animal - (instancetype)initWithAge:(NSInteger)age name:(NSString *)name { if (self = [super init]) { self.age = age; self.name = name; } return self; } + (instancetype)personWithAge:(NSInteger)age name:(NSString *)name { return [[self alloc] initWithAge:age name:name]; } + (void)run { NSLog(@"Animal +run"); } - (void)run { NSLog(@"%zd %@ -run", _age, _name); } + (void)eat:(NSString *)food other:(NSString *)other { NSLog(@"Animal +eat %@ %@", food, other); } - (void)eat:(NSString *)food other:(NSString *)other { NSLog(@"%zd %@ -eat %@ %@", _age, _name, food, other); } @end int sum(int a, int b) { return a + b; }
- 在 {targetName}-Bridging-Header.h 桥接文件里面 导入 OC的文件,如下
#import "Animal.h"
- 调用 Swift 调用 OC 代码
let p = Animal(age: 10, name: "Jack") p.age = 18 p.name = "Rose" // 18 Rose -run p.run() // 18 Rose -eat Apple Water p.eat("Apple", other: "Water") // Animal +run Animal.run() // Animal +eat Pizza Banana Animal.eat("Pizza", other: "Banana") // 30 print(sum(10, 20))
- Swift 调用
@_silgen_name
如果我们在C 语言有一个方法和 swift 里面的方法重名,那么在调用的时候,在Swift项目里面会 优先调用 swift 里面的方法,如果我们也想调在Swift里面调用C的方法,我们可以使用@_silgen_name
修改 C 函数名
// C语言 int sum(int a, int b) { return a + b; } // Swift @_silgen_name("sum") func swift_sum(_ v1: Int32, _ v2: Int32) -> Int32 print(swift_sum(10, 20)) // 30 print(sum(10, 20)) // 30
- 2.3、OC 调用 Swift
Xcode已经默认生成一个用于OC调用Swift的头文件,文件名格式是:{targetName}-Swift.h
- (1)、在 OC 的文件里面导入
{targetName}-Swift.h
文件, - (2)、要求 Swift要在OC里面使用的类继承于
NSObject
,如果 Swift 类里面的某个成员或者方法我们想要暴露给外面,就要在 某个成员或者方法 前面加@objc
- (3)、使用
@objcMembers
修饰类代表默认所有成员都会暴露给OC(包括扩展中定义的成员)最终是否成功暴露,还需要考虑成员自身的访问级别
@objcMembers class Car: NSObject { var price: Double var band: String init(price: Double, band: String) { self.price = price self.band = band } func run() { print(price, band, "run") } static func run() { print("Car run") } } extension Car { func test() { print(price, band, "test") } }
提示:Xcode会根据Swift代码生成对应的OC声明,写入{targetName}-Swift.h 文件
- 上述代码在编译后就会在
{targetName}-Swift.h
文件 中生成如下代码
@interface Car : NSObject @property (nonatomic) double price; @property (nonatomic, copy) NSString * _Nonnull band; - (nonnull instancetype)initWithPrice:(double)price band:(NSString * _Nonnull)band OBJC_DESIGNATED_INITIALIZER; - (void)run; + (void)run; - (nonnull instancetype)init SWIFT_UNAVAILABLE; + (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable"); @end @interface Car (SWIFT_EXTENSION(JKSwiftDemo)) - (void)test; @end
- 在 OC 文件中使用Swift的类,先导入
#import "targetName-Swift.h"
#import "targetName-Swift.h" Car *c = [[Car alloc] initWithPrice:10.5 band:@"BMW"]; c.band = @"Bently"; c.price = 108.5; [c run]; // 108.5 Bently run [c test]; // 108.5 Bently test [Car run]; // Car run
- 通过
@objc
重命名Swift暴露给OC的符号名(类名、属性名、函数名等),如下
@objc(JKCar) @objcMembers class Car: NSObject { var price: Double @objc(name) var band: String init(price: Double, band: String) { self.price = price self.band = band } @objc(drive) func run() { print(price, band, "run") } static func run() { print("Car run") } } extension Car { @objc(newTest) func test() { print(price, band, "test") } }
- 在OC里面的调用
JKCar *c = [[JKCar alloc] initWithPrice:10.5 band:@"BMW"]; c.name = @"Bently"; c.price = 108.5; [c drive]; // 108.5 Bently run [JKCar run]; // Car run
- 2.4、根据2.2和2.3 列举的几个问题
- 1.为什么 Swift 暴露给 OC 的类最终要继承自 NSObject?
答:因为 在OC 里面用类 ,必然继承于 NSObject,在OC里面调用方法必然还要用到 runtime 那套流程,里面牵扯到 isa指针,isa指针来自NSObject - 2.p.run()底层是怎么调用的?反过来,OC调用 Swift 底层又是如何调用?
答:前面的:OC的东西在Swift里面调用,我们可以看到调用了runtime那套机制;后面的:Swift的东西在OC里面调用,打断点看汇编可以发现调用的也是runtime那套机制 - 3.Swift 里面的 car.run() 底层是怎么调用的?
答:走的是Swift那套流程,如果我们强行让它走OC那套runtime机制,可以在 run() 函数前加dynamic
- 2.5、选择器(Selector)
Swift中依然可以使用选择器,使用#selector(name)
定义一个选择器,但是 必须是被@objcMembers
或@objc
修饰的方法才可以定义选择器
@objcMembers class Person: NSObject { func test1(v1: Int) { print("test1") } func test2(v1: Int, v2: Int) { print("test2(v1:v2:)") } func test2(_ v1: Double, _ v2: Double) { print("test2(_:_:)") } func run() { perform(#selector(test1)) perform(#selector(test1(v1:))) perform(#selector(test2(v1:v2:))) perform(#selector(test2(_:_:))) perform(#selector(test2 as (Double, Double) -> Void)) } }