二十、从OC到Swift
2020-02-18 本文已影响0人
爱玩游戏的iOS菜鸟
1、MARK 、TODO、FIXME
- MARK: 类似OC中的#pragma mark
- MARK: - 类似OC中的#pragma mark -
- TODO: 用于标记未完成的任务
- FIXME: 用于标记待修复的问题
TODO: 、FIXME: 可配合#warning("")使用效果更好
func test(){
//TODO:未完成
#warning("TODO:未完成")
}
func test2(){
var age = 10
//FIXME:修复
#warning("FIXME:修复")
}
public class Person {
//MARK: - 属性
var age = 0
var weight = 0
var height = 0
//MARK: - 私有方法
//MARK: 跑
private func run1(){
}
private func run2(){
}
//MARK: 吃
private func eat1(){
}
private func eat2(){
}
//MARK: - 公共方法
//MARK: 走
public func walk1(){
}
public func walk2(){
}
}
3、条件编译
//操作系统:macOS、iOS、tvOS、watchOS、Linux、Android、Windows、FreeBSD
#if os(macOS) || os(iOS)
//CPU架构:i386\x86_64\arn\arm64
#elseif arch(x86_64) || arch(arm64)
//swifta版本
#elseif swift(<5) && swift(>=3)
//模拟器
#elseif targetEnvironment(simulator)
//可以导入某模块
#elseif canImport(Foundation)
#else
#endif
自定义标记
#if DEBUG
//debug模式
#else
//release模式
#endif
#if TEST
//debug模式
#else
//release模式
#endif
#if OTHER
//debug模式
#else
//release模式
#endif
4、打印
//打印文件路径,代码行数,打印信息
func ZQlog<T>(_ msg: T, file: NSString = #file, line: Int = #line, fn: String = #function) {
#if DEBUG
let prefix = "\(file.lastPathComponent)_\(line)_\(fn)"
print(prefix,msg)
#else
#endif
}
5、系统版本检测
系统版本检测@available(iOS 10, macOS 10.15, *)
class Person { }//在低于某些版本即不可用
struct Student {
@available(*, unavailable, renamed: "study")
func study_() { }//方法已不可用,使用study方法
func study() { }
@available(iOS, deprecated: 11)
@available(macOS, deprecated: 10.12)
func run() { }//方法版本已弃用
}
var stu = Student()
stu.study()
stu.run()//警告:'run()' was deprecated in macOS 10.12
var person = Person()//报错:'Person' is only available in macOS 10.15 or newer
更多用法参考:API可用性更多用法
6、iOS程序的入口
- 在Appdelegate上面默认有一个@UIApplicationMain标记,这表示:
- 编译器自动生成入口代码(main函数代码,自动设置Appdelegate)为APP的代理
- 也可以删掉@UIApplicationMain,自定义入口代码:新建一个main.swift文件
//
// main.swift
// TestIOS
//
// Created by apple on 2020/2/16.
// Copyright © 2020 apple. All rights reserved.
//
import UIKit
class ZQUIApplication: UIApplication {
}
UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, NSStringFromClass(ZQUIApplication.self), NSStringFromClass(AppDelegate.self))
7、Swift调用OC代码
- 新建一个桥接头文件,文件名格式默认为:targetName-Bridging-Header
桥接头文件
新建OC文件,也会自动生成桥接文件
- 在targetName-Bridging-Header文件中#import OC需要暴露给Swift的内容
Person.h
//C语言
int sum(int a, int b);
//OC对象
@interface Person : NSObject
@property(nonatomic, copy)NSString *name;
@property(nonatomic, assign) NSInteger age;
- (instancetype)initWithAge:(NSInteger)age withName:(NSString *)name;
+ (instancetype)personWithAge:(NSInteger)age withName:(NSString *)name;
- (void)run;
+ (void)run;
- (void)eat:(NSString *)food other:(NSString *)other;
+ (void)eat:(NSString *)food other:(NSString *)other;
@end
Person.m
#import "Person.h"
@implementation Person
-(instancetype)initWithAge:(NSInteger)age withName:(NSString *)name{
if (self = [self init]) {
self.age = age;
self.name = name;
}
return self;
}
+(instancetype)personWithAge:(NSInteger)age withName:(NSString *)name{
return [[self alloc]initWithAge:age withName:name];
}
+(void)run{
NSLog(@"Person + run");
}
-(void)run{
NSLog(@"%zd %@ - run",_age,_name);
}
+(void)eat:(NSString *)food other:(NSString *)other{
NSLog(@"Person +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;
}
Swift调用OC对象
var person = Person(age: 10, withName: "Jack")
person.age = 18
person.name = "Rose"
person.run()//输出:18 Rose - run
person.eat("Apple", other: "Android")//输出:Rose -eat Apple Android
Person.run()//输出:Person + run
Person.eat("Java", other: "C++")//输出:Person +eat Java C++
print(sum(10, 20))//输出:30
- 如果C语言暴露给Swift的函数名跟Swift中的其他函数名冲突了,可以再Swift中使用@_silgen_name修改C函数名
//调用C函数时 修改C函数名的同时 保证参数类型,返回值与原函数一致
@_silgen_name("sum") func swift_sum(_ v1: Int32, _ v2: Int32) -> Int32
print(swift_sum(10, 20))//输出:30
8、OC调用Swift代码
- Xcode已经默认生成一个用于OC调用Swift的头文件,文件名格式是:targetName-Swift.h
Xcode已经默认生成,可以调用(虽然项目中看不见) - Swift会暴露给OC的类最终继承自NSObject(只要继承就会暴露,但如果不暴露其初始化器,则无法初始化)
- 使用@objc修饰需要暴露给OC的成员
- 使用@objcMembers修饰类
- 代表默认所有成员都会暴露给OC(包括扩展中定义的成员)
- 最终是否成功暴露,还需要考虑成员自身的访问级别
Swift —— Car声明及扩展
@objcMembers class Car: NSObject {//@objcMembers标记 暴露所有成员(方法,属性)
var price: Double
var band: String//@objc 标记单个属性、方法暴露给OC
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")
}
}
testSwift()
- Xcode会根据swift生成对应的OC声明,写入taretName-Swift.h 文件
编译后,example-Swift中生成对应的OC代码供OC文件使用
.h
void testSwift();
.m
#import "example-Swift.h"
void testSwift(){
NSLog(@"testSwift");
Car *car = [[Car alloc] initWithPrice:110000 band:@"benz"];
car.price = 120000;
car.band = @"audi";
[car run];
[car test];
[Car run];
}
- 可以通过@objc重命名swift暴露给OC的符号名(类名、属性名、函数名等) ——相应生成的taretName-Swift.h 文件也会改动,调用方式也一并更改
@objc(ZQCar)
@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(fix)
func test() {
print(price,band,"test")
}
}
选择器
- Swift中依然可以使用选择器,使用#seletor(name)定义一个选择器
- 前提是必须被@objcMambers或@objc修饰的方法才可以定义选择器
@objc(Swift_Person)
@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(v1:v2:)")
}
func run() {
perform(#selector(test1))
perform(#selector(test1(v1:)))
perform(#selector(test2(_:_:)))
perform(#selector(test2(v1:v2:)))
perform(#selector(test2 as (Double, Double) -> Void))
}
}
String(具体用法见第三章 此处只讲SubString及String)
Substring
- Swift的字符串类型String,与OC的NSString,在Api设计上还是有很大差异的
- String可以通过下标、prefix、subffix等截取字符串,子串类型不是String,而是Substring
- Substring与它的base String共享字符串数据
- Substring发生修改或转为String时,会分配新的内存存储字符串数据
var str = "1_2_3_4_5#6*7"
var substr1 = str.prefix(3)//String.SubSequence类型 Substring
var substr2 = str.suffix(3)
print(substr1,substr2)//输出:1_2 6*7
var rang = str.startIndex ..< str.index(str.startIndex, offsetBy: 4)
var substr3 = str[rang]
//substring.base 原字符串
print(substr3,substr3.base)//1_2 6*7 1_2_ 1_2_3_4_5#6*7
//如果对substring进行修改或转为string时,会重新分配内存 也不会影响原字符串
substr3.append(contentsOf: "zzz")
print(substr3,substr3.base)////输出:1_2_zzz 1_2_zzz
var str2 = String(substr3)//Substring转String
String相关的协议
- BidirectionalCollection协议包含内容
- startIndex、endIndex属性,Index方法
- String、Array都遵守了这个协议
- RangeReplaceableCollection协议包含的内容
- replaceSubrange 、append、insert、remove方法
- String、Array都遵守了这个协议
- Dictionary、Set也有实现上述协议中声明的一些方法,只是并没有遵守上述协议
String与NSString
- String与NSString之间可以随时随地的桥接转换
var str1: String = "Jack"
var str2: NSString = "rose"
var str3: NSString = str1 as NSString//底层是调用了桥接函数的 且str1修改 str3不受影响
var str4: String = str2 as String
var str5 = str3.substring(with: NSRange(location: 0, length: 2))//String
- 比较字符串等价
- String使用==运算符
- NSString使用isEqual方法,也可以使用==运算符(本质还是调用了isEqual)
Swift、OC桥接转换表
桥接转换表协议
只能被class继承的协议
- 被@objc修饰的协议,还可以暴露给OC去遵守实现
protocol Procotol1: AnyObject {
}
protocol Procotol2: class {
}
@objc protocol Procotol3 {
}
可选协议
- 可以通过@objc定义可选协议,这种协议只能被class遵守
- 也可以使用扩展实现协议,达到可选协议的效果
@objc protocol Runnable {
func run1()
@objc optional func run2()
func run3()
}
class Dog: Runnable {
//Runnable只能被类遵守 run2()可以不用实现
func run1(){
}
func run3() {
}
}
dynamic
- 被@objc dynamic 修饰的内容会有动态性,比如调用方法会走runtime那一套流程
class Dog: NSObject {
@objc dynamic func test1() {
}
func test2() {
}
}
var dog = Dog()
dog.test1()//OC runtime那一套 msgSend消息机制
dog.test2()//Swift 虚表
KVC/KVO
- Swift支持KVC \ KVO的条件
- 属性所在的类、监听器最终继承自NSObject
- 用@ibjc dynamic修饰对应的属性
class Observer: NSObject {
override class func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("observeValue",change?[.newKey] as Any)
}
}
class Student: NSObject {
@objc dynamic var age :Int = 0
var observer:Observer = Observer()
override init() {
super.init()
self.addObserver(observer, forKeyPath: "age", options: .new, context: nil)
}
deinit {
self.removeObserver(observer, forKeyPath: "age", context: nil)
}
}
var person = Student()
person.age = 20
person.setValue(25, forKey: "age")
block方式的KVO
class Student: NSObject {
@objc dynamic var age:Int = 0
var observation:NSKeyValueObservation?
override init() {
super.init()
observation = observe(\Student.age, options: .new, changeHandler: { (person, change) in
print(change.newValue as Any)
})
}
}
var stu = Student()
stu.age = 20//输出:Optional(20)
stu.setValue(25, forKey: "age")//输出:Optional(25)
关联对象(Associated Object)
- Swift对象中,class依然可以使用关联对象
- 默认情况下,extension不可以增加存储属性
- 借助关联对象,可以实现extension为class增加存储属性的效果
class Student {
}
extension Student{
private static var AGE_KEY:Void?
var age: Int{
get{
((objc_getAssociatedObject(self, &Self.AGE_KEY)) as? Int) ?? 0
}
set{
(objc_setAssociatedObject(self, &Self.AGE_KEY, newValue, .OBJC_ASSOCIATION_ASSIGN))
}
}
}
资源名管理
-
这种做法实际参考了Andriod的资源名管理方式
资源名调用
添加响相应的扩展
多线程开发
异步
DispatchQueue.global().async {
//异步操作
DispatchQueue.main.async {
//回到主线程
}
}
//使用DispatchWorkItem
let item = DispatchWorkItem {
print("先执行")
}
DispatchQueue.global().async(execute: item)
item.notify(queue: DispatchQueue.main) {
print("后执行")
}
延迟
//主线程延迟second
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3.0) {
print("333")
}
异步延迟
let item = DispatchWorkItem {
print("异步延迟执行")
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3.0, execute: item)
item.notify(queue: DispatchQueue.main) {
print("回到主线程执行")
}
//可以取消
item.cancel()
下面对上面的进行简单的封装:
//封装.swift文件
public typealias Task = () -> Void
struct Asyncs {
//传一个异步任务
public static func async(task: @escaping Task){
_async(task: task)
}
//传一个异步任务 和 异步任务完成后回到主线程需要做的任务
public static func async(task: @escaping Task, _ mainTask: @escaping Task){
_async(task: task, mainTask)
}
private static func _async(task: @escaping Task, _ mainTask: Task? = nil){
let item = DispatchWorkItem(block: task)
DispatchQueue.global().async(execute: item)
if let main = mainTask {
item.notify(queue: DispatchQueue.main, execute: main)//
}
}
//延迟
@discardableResult
public static func delay(_ second:Double, _ block: @escaping Task) -> DispatchWorkItem{
let item = DispatchWorkItem(block: block)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + second, execute: item)
return item
}
//异步延迟
@discardableResult
public static func asyncDelay(_ seconds: Double, _ task:@escaping Task) -> DispatchWorkItem{
//TODO:
_asyncDelay(seconds, task)
}
//异步延迟
@discardableResult
public static func asyncDelay(_ seconds: Double, _ task:@escaping Task, _ maintask: @escaping Task) -> DispatchWorkItem{
//TODO:
_asyncDelay(seconds, task, maintask)
}
private static func _asyncDelay(_ seconds: Double, _ task:@escaping Task, _ maintask: Task? = nil) -> DispatchWorkItem{
let item = DispatchWorkItem(block: task)
DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + seconds, execute: item)
if let main = maintask{
item.notify(queue: DispatchQueue.main, execute: main)
}
return item
}
}
//调用
//异步线程
Asyncs.async {
print("1",Thread.current)//1 异步线程
}
//异步线程 回到主线程执行下一任务
Asyncs.async(task: {
print("2",Thread.current)//2 异步线程
}) {
print("3",Thread.current)//3 主线程
}
//主线程延迟
Asyncs.delay(3.0) {
print("4",Thread.current)
}
//异步延迟
Asyncs.asyncDelay(4.0) {
print("异步延迟",Thread.current)
}
Asyncs.asyncDelay(5.0, {
print("异步延迟",Thread.current)
}) {
print("异步延迟,回到主线程",Thread.current)
}
once
- swift中已经废弃了dispatch_once
- 可以用类型属性或者全局变量\常量,因为默认自带lazy + dispatch_once效果
此处可以查看属性章节-属性
加锁
- gcd信号量(方法1)
- Foundation(方法2)
public struct Cache{
private static var data = [String: Any]()
// private static var lock = DispatchSemaphore(value: 1) 方法1:
// private static var lock = NSLock() 方法2:
// private static var lock = NSRecursiveLock() 递归锁
public static func get(_ key: String) -> Any?{
data[key]
}
// public static func set(_ key: String, _ value: Any){ 方法1:
// lock.wait()
// defer {
// lock.signal()
// }
// data[key] = value
// }
// public static func set(_ key: String, _ value: Any){ 方法2:
// lock.lock()
// defer {
// lock.unlock()
// }
// data[key] = value
// }
}