從OC到Swift (一)

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標(biāo)記,表示

  • 編譯器自動(dòng)生成入口代碼(main函數(shù)代碼),自動(dòng)設(shè)置AppDelegate為APP的代理
  • 也可以刪掉@main,自定義入口代碼,新建一個(gè)main.swift文件(需要自定義application需求時(shí)可以自己寫(xiě)入口代碼),main.swift文件代碼如下:
import Foundation
import UIKit

class MJApplication: UIApplication {
    
}
UIApplicationMain(CommandLine.argc,
                  CommandLine.unsafeArgv,
                  NSStringFromClass(MJApplication.self),
                  NSStringFromClass(AppDelegate.self))

由于很多常用的第三方框架還是oc版本,沒(méi)有swift版本,但是項(xiàng)目里可能需要調(diào)用那些框架的功能,所以swift語(yǔ)言的工程還需要用到oc版本的三方,或者公司項(xiàng)目需要用到swift和oc混合開(kāi)發(fā)時(shí),就涉及到了Swift調(diào)用OC或者OC調(diào)用Swift

Swift調(diào)用OC

  • 手動(dòng)新建一個(gè)橋接頭文件,文件名格式默認(rèn)為:{targetName}-Bridging-Header.h,然后在Build Settings設(shè)置路徑,如下
  • 或者在swift工程里新建一個(gè)oc的文件時(shí)會(huì)自動(dòng)彈出彈框詢問(wèn)是否自動(dòng)創(chuàng)建橋接文件,如下


    想用oc的哪些頭文件,在橋接頭文件中導(dǎo)入頭文件即可,例#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調(diào)用Swift

  • Xcode已經(jīng)默認(rèn)生成一個(gè)用于OC調(diào)用Swift的頭文件,文件名格式是:{targetName}-Swift.h
    這里需要注意的是:工程名如果帶有中劃線-時(shí),buildsettings的路徑變成了下劃線_,import引用時(shí),寫(xiě)下劃線的文件名即可;工程名如果帶有下劃線_時(shí),buildsettings的路徑也是下劃線,引用時(shí)會(huì)報(bào)錯(cuò)找不到文件(不知道為啥,難道是項(xiàng)目名稱命名不允許用下劃線???)所以為了避免問(wèn)題吧,項(xiàng)目名稱還是乖乖用大小駝峰吧?。。」?,文件內(nèi)部放的是swift要暴露給oc的代碼,那么也不是所有的swift代碼都要暴露給oc,要暴漏給oc的代碼要繼承NSObject
  • Swift暴露給OC的類最終繼承自NSObject(why:因?yàn)閛c有runtime,runtime是要求類有isa指針,isa指針肯定是繼承NSObject才有的,所以最終要繼承NSObject)
  • 使用@objc修飾需要暴露給OC的成員
  • 使用@objcMembers修飾類
    - 代表默認(rèn)所有成員都會(huì)暴露給oc(包括擴(kuò)展中定義的成員)
    - 最終是否成功暴露,還需要考慮成員自身的訪問(wèn)級(jí)別
    例子如下:
    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]; // 報(bào)錯(cuò):No visible @interface for 'Car' declares the selector 'run'
[Car run]; //報(bào)錯(cuò):No known class method for selector 'run'
[car test];
被@objc修飾的屬性或者方法才能被調(diào)用

也可寫(xiě)成如下:
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會(huì)根據(jù)Swift代碼生成對(duì)應(yīng)的OC聲明,寫(xiě)入 {targetName}-Swift.h文件
  • 可以通過(guò)@objc重命名Swift暴露給OC的符號(hào)名(類名、屬性名、函數(shù)名等)
    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)定義一個(gè)選擇器
  • 必須是被@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()底層是怎么調(diào)用的?
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()

從匯編來(lái)看,走runtime的機(jī)制,objc_msgSend

反過(guò)來(lái),OC調(diào)用Swift底層又是如何調(diào)用?
swift寫(xiě)的類繼承自NSObject類,暴漏給OC調(diào)用,同樣走runtime那套機(jī)制,objc_msgSend

car.run()底層是怎么調(diào)用的?
第一種情況:

@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文件里邊調(diào)用,沒(méi)有走oc的runtime機(jī)制,如果想要走objc_msgSend,則在run方法前邊加上dynamic關(guān)鍵字修飾

第二種情況(不繼承自NSObject,并且沒(méi)有@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類方法調(diào)用是走虛表v-Table那一套機(jī)制

字符串

  • Swift的字符串類型String,跟OC的NSString,在API設(shè)計(jì)上還是有較大差異
//空字符串
var emptyStr1 = ""
var emptyStr2 = String()

var str:String = "1"
//拼接,
str.append("_2")
//重載運(yùn)算符 +
str = str + "_3"
//重載運(yùn)算符 +=
str += "_4"
//\()插值
str = "\(str)_5"
//長(zhǎng)度
print(str.count)

var str = "1_2"
print(str.count,str.startIndex,str.endIndex)
//打印結(jié)果: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可以通過(guò)下標(biāo)、prefixsuffix等截取子串,子串類型不是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,共享字符串?dāng)?shù)據(jù)
  • Substring轉(zhuǎn)為String時(shí),會(huì)重新分配新的內(nèi)存存儲(chǔ)字符串?dāng)?shù)據(jù)

String與Character

for c in "jack" {//c是Character類型
    print(c)
}
var str = "jack"
//c是Character類型
var c = str[str.startIndex]

String相關(guān)的協(xié)議

  • BidirectionalCollection協(xié)議包含的部分內(nèi)容
    - startIndex、endIndex屬性、index方法
    - String、Array都遵守了這個(gè)協(xié)議
  • RangeReplaceableCollection 協(xié)議包含的部分內(nèi)容
    - append、insertremove方法
    - String、Array都遵守了這個(gè)協(xié)議
  • DictionarySet 也有實(shí)現(xiàn)上述協(xié)議中聲明的一些方法,只是并沒(méi)有遵守上述協(xié)議

多行String

  • ”“”開(kāi)頭,用“”“結(jié)尾
  • 如果要顯示3個(gè)引號(hào),至少轉(zhuǎn)義1個(gè)引號(hào)
let str = """
            Escaping the first quote \"""
            Escaping two quote \"\""
            Escaping all three quote \"\"\"
            """
  • 縮進(jìn)以結(jié)尾的3引號(hào)為對(duì)齊線
let strA = """
        1
    3       2
       4
    
    """
print(strA)
//打印結(jié)果:
    1
3       2
   4

String與NSString

  • StringNSString之間可以隨時(shí)隨地橋接轉(zhuǎn)換(中間是有調(diào)函數(shù)bridge的,不是編譯器直接轉(zhuǎn)的)
    - 如果覺(jué)得String的API過(guò)于復(fù)雜難用,可以考慮將String轉(zhuǎn)為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)
  • 比較字符串內(nèi)容是否等價(jià)
    - String使用 == 運(yùn)算符
    - NSString使用isEqual方法,也可以使用 == 運(yùn)算符(本質(zhì)還是調(diào)用了isEqual方法)
  • String不能轉(zhuǎn)為NSMutableString;但是NSMutableString可以轉(zhuǎn)為String(原因:StringNSString可以互相橋接,String不能橋接轉(zhuǎn)換成NSMutableString,NSMutableString是繼承自NSString的,NSString可以轉(zhuǎn)換成String,所以NSMutableString可以轉(zhuǎn)換成String)(注意:這里指的不能轉(zhuǎn)是不能直接用as橋轉(zhuǎn))

Swift、OC橋接轉(zhuǎn)換表

swift中,類繼承自NSObject和沒(méi)有繼承自NSObject的內(nèi)存區(qū)別:

class Person{
  var age = 10
  var weight = 20
}
var p = Person()
p的內(nèi)存結(jié)構(gòu)為,第一個(gè)八個(gè)字節(jié)是metadata,第二個(gè)八個(gè)字節(jié)是refcount,第三個(gè)八個(gè)字節(jié)是存放的age,第四個(gè)八個(gè)字節(jié)存放的是weight

class Person:NSObject{
  var age = 10
  var weight = 20
}
var p = Person()
p的內(nèi)存結(jié)構(gòu)為第一個(gè)八個(gè)字節(jié)存放的是isa指針,第二個(gè)八個(gè)字節(jié)是age,第三個(gè)八個(gè)字節(jié)是weight,第四個(gè)八個(gè)字節(jié)是湊數(shù)(因?yàn)槎芽臻g分配的內(nèi)存長(zhǎng)度需要是16的倍數(shù))
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容