oc的入口:main.m
文件中
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
swift的入口:AppDelegate.swift
文件中顶部@main
标记,表示
- 编译器自动生成入口代码(
main
函数代码),自动设置AppDelegate
为APP的代理 - 也可以删掉
@main
,自定义入口代码,新建一个main.swift
文件(需要自定义application需求时可以自己写入口代码),main.swift
文件代码如下:
import Foundation
import UIKit
class MJApplication: UIApplication {
}
UIApplicationMain(CommandLine.argc,
CommandLine.unsafeArgv,
NSStringFromClass(MJApplication.self),
NSStringFromClass(AppDelegate.self))
由于很多常用的第三方框架还是oc版本,没有swift版本,但是项目里可能需要调用那些框架的功能,所以swift语言的工程还需要用到oc版本的三方,或者公司项目需要用到swift和oc混合开发时,就涉及到了Swift调用OC或者OC调用Swift
Swift调用OC
- 手动新建一个桥接头文件,文件名格式默认为:{targetName}-Bridging-Header.h,然后在Build Settings设置路径,如下
- 或者在swift工程里新建一个oc的文件时会自动弹出弹框询问是否自动创建桥接文件,如下
想用oc的哪些头文件,在桥接头文件中导入头文件即可,例#import "Person.h"
,则在swift工程中,Person
类就暴漏给swift使用了
Person.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
int sum(int a,int b);
@interface Person : 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
Person.m
#import "Person.h"
@implementation Person
- (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(@"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文件中Swift代码
let p = Person(age: 10, name: "jack")
p.age = 18
p.name = "Rose"
p.run() //18 Rose - run
p.eat("Apple", other: "water")//18 Rose - eat Apple water
Person.run() //Person + run
Person.eat("Pizza", other: "Banana")//Person + eat Pizza Banana
print(sum(10, 20)) //30
OC调用Swift
- Xcode已经默认生成一个用于OC调用Swift的头文件,文件名格式是:{targetName}-Swift.h
这里需要注意
的是:工程名如果带有中划线-
时,buildsettings的路径变成了下划线_
,import引用时,写下划线的文件名即可;工程名如果带有下划线_
时,buildsettings的路径也是下划线,引用时会报错找不到文件(不知道为啥,难道是项目名称命名不允许用下划线???)所以为了避免问题吧,项目名称还是乖乖用大小驼峰吧!!!哈哈哈,文件内部放的是swift要暴露给oc的代码,那么也不是所有的swift代码都要暴露给oc,要暴漏给oc的代码要继承NSObject - Swift暴露给OC的类最终继承自
NSObject
(why:因为oc有runtime,runtime是要求类有isa
指针,isa
指针肯定是继承NSObject
才有的,所以最终要继承NSObject
) - 使用
@objc
修饰需要暴露给OC的成员 - 使用
@objcMembers
修饰类
- 代表默认所有成员都会暴露给oc(包括扩展中定义的成员)
- 最终是否成功暴露,还需要考虑成员自身的访问级别
例子如下:
swift文件:
class Car: NSObject {
var price:Double
var band:String
@objc 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{
@objc func test(){
print(price,band,"test")
}
}
oc文件:
Car *car = [[Car alloc] initWithPrice:1.55 band:@"bannana"];
[car run]; // 报错:No visible @interface for 'Car' declares the selector 'run'
[Car run]; //报错:No known class method for selector 'run'
[car test];
被@objc修饰的属性或者方法才能被调用
也可写成如下:
swift文件:
@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")
}
}
oc文件:
Car *car = [[Car alloc] initWithPrice:1.55 band:@"bannana"];
[car run];
[Car run];
[car test];
- Xcode会根据Swift代码生成对应的OC声明,写入 {targetName}-Swift.h文件
- 可以通过
@objc
重命名Swift暴露给OC的符号名(类名、属性名、函数名等)
swift文件:
@objc(XXCar)
@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(exec)
func test(){
print(price,band,"test")
}
}
oc文件:
XXCar *car = [[XXCar alloc] initWithPrice:1.55 band:@"bannana"];
car.name = @"banban";
[car drive];
[car exec];
[XXCar run];
选择器(Selector)
- Swift中依然可以使用选择器,使用
#selector(name)
定义一个选择器 - 必须是被
@objcMembers
或@objc
修饰的方法才可以定义选择器。
selector(选择器)是依赖于runtime的,oc里才有runtime,纯swift里是不存在runtime的
@objcMembers class XXPerson: 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))
}
}
p.run()底层是怎么调用的?
oc文件:
Person.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic,assign) NSInteger age;
@property (nonatomic,copy) NSString *name;
- (instancetype)initWithAge:(NSInteger)age name:(NSString *)name;
- (void)run;
@end
Person.m
#import "Person.h"
#import "ludan-Swift.h"
@implementation Person
- (instancetype)initWithAge:(NSInteger)age name:(NSString *)name{
if (self = [super init]) {
self.age = age;
self.name = name;
}
return self;
}
- (void)run{NSLog(@"%zd %@-run",_age,_name);}
@end
swift文件:
var p:Person = Person(age: 10, name: "jack")
p.run()
从汇编来看,走runtime的机制,objc_msgSend
反过来,OC调用Swift底层又是如何调用?
swift写的类继承自NSObject类,暴漏给OC调用,同样走runtime那套机制,objc_msgSend
car.run()底层是怎么调用的?
第一种情况:
@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")
}
}
var car:Car = Car(price: 10.5, band: "Audi")
car.run()
在swift文件里边调用,没有走oc的runtime机制,如果想要走objc_msgSend,则在run方法前边加上dynamic
关键字修饰
第二种情况(不继承自NSObject,并且没有@objcMembers
修饰):
class Car{
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")
}
}
var car:Car = Car(price: 10.5, band: "Audi")
car.run()
纯swift类方法调用是走虚表v-Table那一套机制
字符串
- Swift的字符串类型
String
,跟OC的NSString
,在API设计上还是有较大差异
//空字符串
var emptyStr1 = ""
var emptyStr2 = String()
var str:String = "1"
//拼接,
str.append("_2")
//重载运算符 +
str = str + "_3"
//重载运算符 +=
str += "_4"
//\()插值
str = "\(str)_5"
//长度
print(str.count)
var str = "1_2"
print(str.count,str.startIndex,str.endIndex)
//打印结果:3 Index(_rawBits: 1) Index(_rawBits: 196609)
//1_2_
str.insert("_", at: str.endIndex)
//1_2_3_4
str.insert(contentsOf: "3_4", at: str.endIndex)
//1666_2_3_4
str.insert(contentsOf: "666", at: str.index(after: str.startIndex))
//1666_2_3_8884
str.insert(contentsOf: "888", at: str.index(before: str.endIndex))
//1666hello_2_3_8884
str.insert(contentsOf: "hello", at: str.index(str.startIndex, offsetBy: 4))
//666hello_2_3_8884
str.remove(at: str.firstIndex(of: "1")!)
//hello_2_3_8884
str.removeAll{ $0 == "6"}
var range = str.index(str.endIndex, offsetBy: -4)..<str.index(before: str.endIndex)
//hello_2_3_4
str.removeSubrange(range)
-
String
可以通过下标、prefix
、suffix
等截取子串,子串类型不是String
,而是Substring
var str = "1_2_3_4_5"
var substr1 = str.prefix(3)
print(substr1)
var substr2 = str.suffix(3)
print(substr2)
var range = str.startIndex..<str.index(str.startIndex, offsetBy: 3)
var substr3 = str[range]
print(substr3)
//最初的String,1_2_3_4_5
print(substr3.base)
//Substring -> String
var str2 = String(substr3)
-
Substring
和他的base
,共享字符串数据 -
Substring
转为String
时,会重新分配新的内存存储字符串数据
String与Character
for c in "jack" {//c是Character类型
print(c)
}
var str = "jack"
//c是Character类型
var c = str[str.startIndex]
String相关的协议
-
BidirectionalCollection
协议包含的部分内容
-startIndex
、endIndex
属性、index
方法
-String
、Array
都遵守了这个协议 -
RangeReplaceableCollection
协议包含的部分内容
-append
、insert
、remove
方法
-String
、Array
都遵守了这个协议 -
Dictionary
、Set
也有实现上述协议中声明的一些方法,只是并没有遵守上述协议
多行String
- 用
”“”
开头,用“”“
结尾 - 如果要显示3个引号,至少转义1个引号
let str = """
Escaping the first quote \"""
Escaping two quote \"\""
Escaping all three quote \"\"\"
"""
- 缩进以结尾的3引号为对齐线
let strA = """
1
3 2
4
"""
print(strA)
//打印结果:
1
3 2
4
String与NSString
-
String
与NSString
之间可以随时随地桥接转换(中间是有调函数bridge的,不是编译器直接转的)
- 如果觉得String
的API过于复杂难用,可以考虑将String
转为NSString
var str1:String = "jack"
var str2:NSString = "rose"
var str3 = str1 as NSString
var str4 = str2 as String
var str5 = str3.substring(with: NSRange(location: 0, length: 2))
print(str5)
- 比较字符串内容是否等价
-String
使用 == 运算符
-NSString
使用isEqual
方法,也可以使用 == 运算符(本质还是调用了isEqual
方法) -
String
不能转为NSMutableString
;但是NSMutableString
可以转为String
(原因:String
与NSString
可以互相桥接,String
不能桥接转换成NSMutableString
,NSMutableString
是继承自NSString
的,NSString
可以转换成String
,所以NSMutableString
可以转换成String
)(注意:这里指的不能转是不能直接用as
桥转)
Swift、OC桥接转换表
swift中,类继承自NSObject和没有继承自NSObject的内存区别:
class Person{
var age = 10
var weight = 20
}
var p = Person()
p的内存结构为,第一个八个字节是metadata,第二个八个字节是refcount,第三个八个字节是存放的age,第四个八个字节存放的是weight
class Person:NSObject{
var age = 10
var weight = 20
}
var p = Person()
p的内存结构为第一个八个字节存放的是isa指针,第二个八个字节是age,第三个八个字节是weight,第四个八个字节是凑数(因为堆空间分配的内存长度需要是16的倍数)