從Object-C -> Swift3.0

weixin_33866037發表於2016-08-25

前言

當我們開始接觸一門新語言時,我們難免避免不了型別和基本語法規則。我們會急切的想知道如何用新的語法規則寫我們原先所熟知的語句。本文旨在對於Object-C和Swift做一些基本的對比。通過閱讀本文,您能快速的瞭解Swift3.0和Object-C的一些差別。

  • 基本型別
    同所有的面嚮物件語言一樣,Swift裡也只有兩種型別:

1.值型別
2.引用型別

值型別包括struct,enum;
引用型別主要是class,嗯還有閉包也是引用型別哦。

  • 程式碼不再需要分號結尾

Swift程式碼單行結束,不需要強制加分號,但也可以選擇加

  • 型別推斷

Swift可以自動根據上下文為一個變數推斷出其型別;

  • 強型別語言

儘管Swift可以支援型別推斷,可以用var來宣告變數。但Swift是強型別語言。這樣意味著,只要涉及不同型別之間的運算,都需要進行強制型別轉換

  • 變數、常量定義

let constant name : type = expression
var variable name : type = expression

在開始所有介紹前,我們先看簡單的幾行程式碼.大家採一些預期結果是什麼?

    var num=3.0
    var integer=6
    print(num+integer) //輸出結果是?

結果:編譯不過。因為Swift是一個強型別語言


從Object-C向Swift過渡

接下來將會從Protocol,block,GCD等大家在Object-C裡熟知的內容開始,一步步過渡到Swift3.0。

1.protocol
2.block
3.GCD
4.property
5.函式
6.extension,category
7.可選型別?與??
8.型別判斷與轉換
9.id型別與Any,AnyObject,nil
10.typedef與typealias
11.巨集定義
12.混合程式設計
13.API設計與函式命名對比

1.protocol

Swift也和OC一樣有protocol,但不同的是Swift的protocol可以被class,struct,enum實現,甚至還能進行extension擴充套件。
參考:Protocols

  • Protocol以及optional protocol定義
//Object-C
@protocol RefreshHeaderDelegate <NSObject>
@optional
   - (void)willBeginRefresh:(PullRefreshHeader*)header;
   - (void)didRefresh:(PullRefreshHeader*)header;
@required
   - (void)didFinishRefresh:(PullRefreshHeader*)header;
@end

//Swift
@objc protocol RefreshHeaderDelegate {
    @objc optional func willBeginRefresh(header:PullRefreshHeader) -> Void
    @objc optional func didRefresh(header:PullRefreshHeader) -> Void
}

//加了class的protocol才能被使用在類的delegate模式中,因為protocol不加class標記則還可以被enum,struct實現
protocol RefreshHeaderDelegate :class{
    func willBeginRefresh(header:PullRefreshHeader) -> Void
    func didRefresh(header:PullRefreshHeader) -> Void
}
  • 定義了optional方法後,如何判斷delegate是否實現了該方法?
//Object-C
if([self.delegate respondsToSelector:@selector(willBeginRefresh:)]){
  //執行delegate的方法
  [self.delegate willBeginRefresh:self];
}

//Swift中我們可以更優雅的實現,optional是很方便的
self.delegate?.willBeginRefresh?(header: self)

2.block

Swift中任何兩個{}之間的程式碼都算是閉包,因此函式也是一種特殊的閉包。
參考:closure-expressions-in-swift

  • 閉包定義
//Swift閉包定義
{ (obj:AnyObject) ->Void in
  //閉包內容開始
  print(obj)
  pring($0)
  //可以用$<index>來指向閉包的形參,下標從0開始
}
  • 如何避免閉包中的迴圈引用?
//Object-C
__weak __typeof(self) __weak_self = self
[button onClicked:^(MttTopBannerButton * button) {
    MttBottomBanner *banner=[[MttBottomBanner alloc] init];
    [__weak_self.view addSubview:banner];
    [banner show];
    button.hidden=YES;
 }];
 
 //Swift更優雅
button.onClicked({
    [weak self]
    (button:MttTopBannerButton) ->Void in
    var banner = MttBottomBanner()
    self?.view.addSubview(banner)
    banner.show()
    button.hidden=true
})
  • weakself,strongself
//Swift
DispatchQueue.main.sync(execute: {
    [weak self]
    (_:AnyObject?) ->Void in
    self?.draw()
    self?.color=UIColor.white()
    DispatchQueue.main.sync(execute: {
        if let strongSelf=self {
            strongSelf.lock.lock()
            strongSelf.layer.contents=strongSelf.display?.cgImage
            strongSelf.lock.unlock()
        }
    })
})

//請思考為何此處我是NSLock來加鎖?
//因為Swift裡沒有@synchonized關鍵字的替代品了啊,需要的話,得自行呼叫GCD介面實現了.

//Object-C
__weak __typeof(self) __weak_self = self
dispatch_async(dispatch_get_main_queue(),^{
    [self draw];
    self.color=[UIColor whiteColor];
    __typeof(__weak_self) strongSelf = __weak_self
    dispatch_async(dispatch_get_main_queue(),^{
        [strongSelf.lock lock];
        strongSelf.layer.contents=strongSelf.display.CGImage;
        [strongSelf.lock unlock];
    })
});

3.GCD

Grand Dispatch Queue在Object-C中是一組C語言介面,雖然在swif1,swift2中仍保留了這種用法習慣,但它畢竟不太符合一門強型別與物件導向的語言的要求(就像這些人取消了++,--運算子一樣,無時無刻不再想著摒棄老式的C語言程式設計思維),於是Swift3中GCD終於也物件導向了.

  • dispatch_async
//Swift
DispatchQueue.main.async(execute:{blk(nil)})

//Object-C
dispatch_async(dispatch_get_main_queue(),^(id obj){
});
  • dispatch_after
//Object-C
- (void)performBlockOnMainThread:(void (^)())block afterDelay:(CGFloat)delay
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        block();
    });
}

//block引數可為空,因為是optional
func performOnMainThread(_ block:AsyncLoaderTask? ,delay:Double) -> Void {
    if let blk=block {
        let time=DispatchTime.now()+delay
        //DispatchWorkItem相當於之前的dispatch_queue_t所以直接用
        DispatchQueue.main.after(when:time,execute:{blk(nil)})
    }
}

4.Property屬性

在Swift中類裡定義了的變數或常量即是屬性

屬性分為2種:

  • 儲存型屬性
    跟Object-C的property基本差不多。但
  • 計算型屬性
    比較特別,並不是用於真正的儲存一個值或變數,而是提供了getter和setter方法來間接的獲取其他屬性或變數的值。其本身的存在價值是依賴於其它屬性或變數的存在

1.屬性定義
2.屬性觀察器
3.getter,setter
4.delegate模式
5.普通屬性
6.lazy標記
7.optional屬性
8.completion block模式
9.初始化

typealias AsyncLoaderTask = (_:AnyObject?) ->Void
class PullHeaderTable: UITableView {
    
    // MARK: Public Properties
    //////////////
    //1.標準屬性定義方式
    //常量屬性,以及屬性定義的兩種方式,可以隱身讓Swift自行推斷型別,也可以明確指定型別
    let source=PullHeaderTableSource()
    let src : PullHeaderTableSource = PullHeaderTableSource()
    var s : PullHeaderTableSource = PullHeaderTableSource()

    //////////////
    //2.property觀察器
    var data : [AnyObject]?{
        didSet{
            source.data=data
            print(oldValue)
        }
        willSet{
            print(newValue)
        }
    }
    
    //////////////
    //3.getter,setter,通過getter返回的,叫做計算型屬性
    var src : PullHeaderTableSource  {
        get{
            return PullHeaderTableSource()
        }
        set{
            print(newValue)
        }
    }

    //////////////
    //4.delegate模式
    weak var tableDelegate : PulllHeaderTableDelegate?
    
    //////////////
    //5.普通屬性,必須在init方法裡最先被初始化
    var header : UIView
    
    //////////////
    //6.lazy標記,該屬性只會在被呼叫時自動呼叫初始化賦值,而不是在init之前就初始化了,thread unsafe
    lazy var footer = UIView()
    
    //7.optional屬性
    var listener : AnyObject?
    
    //8.optional block屬性(和OC的completion block模式)
    var completion : AsyncLoaderTask?
    
    //////////////
    //9.對於非optional的屬性,在init時必須要初始化,否則會出錯
    //init初始化與OC的不一樣,是先把當前例項的非optional屬性初始化完畢,然後再呼叫父類的init
    //原因在於Swift定義了的任何型別property編譯器都不會給預設值,因此非optional property必須在init中有優先初始化

    init(frame:CGRect) {
        header=UIView()
        super.init(frame:frame, style:UITableViewStyle.plain)
        self.onCreate()
    }
    
    required init?(coder aDecoder: NSCoder) {
        header=UIView()
        super.init(coder: aDecoder)
        self.onCreate()
    }
    
    override init(frame:CGRect,style:UITableViewStyle){
        header=UIView()
        super.init(frame: frame, style: style)
        self.onCreate()
    }
    
    deinit {
        self.dataSource=nil
        self.delegate=nil
    }
    
    // MARK: Private Functions
    private func onCreate() -> Void {
        self.scrollsToTop=false
        self.separatorColor=UIColor.clear()
        self.separatorStyle=UITableViewCellSeparatorStyle.none
        self.contentOffset=CGPoint.zero
        self.delegate=source
        self.dataSource=source
    }
    
}

5.函式

使用 func 來宣告一個函式,使用名字和引數來呼叫函式。使用 -> 來指定函式返回值的型別。
參考:Functions

1.如何讓函式返回多個返回值?
2.如何傳入1個或N個引數
3.重構->函式內巢狀函式
4.函式型別與函式返回值,函式引數

Swift的函式相比較於OC發生了很多很好的改進,我們可以更自由的使用定義函式了.
同時還加入了函式型別。
參考:函式型別

class App: NSObject {
    
    //func test() -> Void {}
    func test() {
        print(calculate(scores: [0,1,2,3,4,5]))
        print(sumOf(numbers: 0,1,2,3,4,5))
        runAnotherFunction(longlongFunction)
    }
    
    // MARK:Function Usage
    //1.使用元組返回多個返回值
    //下述函式從陣列中返回最小,最大,求和.返回值是一個元組.
    //其中呼叫了Swift裡的min,max函式,全域性泛型函式,用於比較大小返回結果.
    //min,max函式類似於OC巨集定義的MIN(),MAX()
    func calculate(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
        var vmin=scores[0]
        var vmax=vmin
        var sum : Int = 0
        for val in scores {
            vmin=min(vmin, val)
            vmax=max(vmax, val)
            sum+=val
        }
        return (vmin,vmax,sum)
    }
    
    //2.傳入可變個數的引數
    func sumOf(numbers: Int...) -> Int {
        var sum=0
        for val in numbers {
            sum+=val
        }
        return sum
    }
    
    //3.巢狀函式,用於部分程式碼的重構,其實也可以裡面定義一個block一樣的。不過比OC多了個選擇
    func longlongFunction() {
        var x=10
        //猜猜看 _ 這裡下劃線的作用?看看如何呼叫的你就會明白了
        func increament(_ val:Int) -> Int {
            return val+1
        }
        print(increament(x))
        func add() {
            //x++
            //Swift 3.0不支援++,--了哦
            x=x+1
        }
        add()
    }
    
    //4.函式作為引數,當然函式也可以作為返回值的.函式型別
    func runAnotherFunction(_ fun: () -> ()) {
        fun()
    }
}

6.extension,category

Swift中沒有category的概念,因為Swift中的extension基本涵蓋了所有原先Object-C中需要的一切

1.新增計算型屬性和計算型靜態屬性
2.定義例項方法和型別方法
3.提供新的構造器
4.定義下標[]
5.定義和使用新的巢狀型別
6.支援協議

參考:下標運算子

extension String {
    func toHex() -> String {
        return ""
    }
}

protocol AppProtocol: class {
    func app() -> Void
}

class App: NSObject {
    var data : Array = Array<Int>()
    static var instance = App()
    private override init() {
        for i in 0...8 {
            self.data.append(i)
        }
    }
}

extension App: AppProtocol {

    //////////////
    //1.新增計算性屬性,儲存型的不可以啊
    var hashCode : Int {
        return Int(Date.timeIntervalSinceReferenceDate)+8
    }
    
    //非法,儲存性屬性不能再extension裡存在
    //var newProperty : Int = 0
    
    //////////////
    //2.新增新的建構函式
    convenience init(_ id:Int, time:Int) {
        self.init()
    }
    
    //////////////
    //3.函式或實現協議
    func app() -> Void {
        print("app")
    }
    
    //////////////
    //4.為物件實現下標運算子
    subscript(index: Int) -> Int {
        get{
            return self.data[index]
        }
        set{
            self.data[index]=newValue
        }
    }
    
    //////////////
    //5.巢狀型別,class,enum,也就是相當於定義內部類
    class UI : NSObject {
    }
}

func testApp() -> Void {
    let app = App.instance
    app[0]=1
    print(app[1])
}

7.可選型別?與??

可空鏈式呼叫(Optional Chaining)
這個名詞太專業了,我們就簡單的來談談吧

定義屬性或變數時,如果要允許一個屬性為空即nil,像Object-C一樣的話。那麼我們需要制定其位optional型別
只需定義結束末尾在加一個?符號即可,如下

var obj : AnyObject?
//定義了一個相當於Object-C的 id obj=nil;東西

對於optional型別的變數或方法(如optional protocol method)我們在呼叫時需要把optional型別的值/物件取出來,這一步叫做unwrap;反之定義時就叫wrap;
通過?來unwrap或者直接利用!來強制unwrap(強制的前提是你能確保變數是有有效值的),如下

var str : String?
str="value"
//前面已經為str賦值過了,所以我們是已知強制unwrap是不會有異常的
if str!.characters.count>0 {
}
    
//如果簽名沒有為str賦值,或者不確定有值與否,需要嘗試unwrap
if str?.characters.count>0 {
}
    
var count : Int
count=str?.characters.count ?? 0
print(count)

大家是否在好奇上述的??符號到底是什麼意思呢?很簡單,如果你理解選擇表示式,那麼可以把它看做對於optional型別的選擇表示式吧
等價於如下程式碼

if str != nil {
    count=str!.characters.count
}
else
{
    count=0
}

8.型別判斷與轉換

1.在Object-C中我們經常會對一個id型別或者不確定具體型別的例項物件做型別判斷,如isKindOfClass;在Swift中我們可以更簡單來實現

參考:Type Casting

is型別檢查運算子

//Object-C判斷型別
UIView *v=XXX;
if([v isKindOfClass:[UIWindow class])
{
}

//Swift
var v=XXX
if v is UIWindow {
}

2.在Object-C中我們要做型別轉換可以直接使用C語言的方式來強制型別賦值,但在Swift中需要用as運算子實現

as 用於向上轉型(upcasts)
as? 和as! 用於向下轉型(Downcasting)

備註:
1.向上轉型:由派生類轉為父類物件
2.向下轉型:有父類物件轉為具體子類物件

當不確定型別轉換是否成功時,用型別轉換的條件形式( as? )
as?如果失敗會返回nil
當確定一定是時,用強制形式式( as! )

//Object-C
for(UIViewController *ct in controllers)
{
    if([ct isKindOfClass:[MttBaseViewController class]])
    {
        NSLog(@"MttBaseViewController");
    }
    else if([ct isKindOfClass:[MttRootViewController class]])
    {
        NSLog(@"MttRootViewController");
    }
}

//Swift
for ct in controllers {
    if let mtt = ct as? MttBaseViewController {
            print("MttBaseViewController")
    } else if let nav = ct as? MttRootViewController {
            print("MttRootViewController")
    }
}

9.id型別與Any,AnyObject,nil

Any 和 AnyObject 是 Swift 中兩個妥協的產物;

AnyObject 可以代表任何 class 型別的例項
Any 可以表示任意型別,甚至包括方法 (func) 型別

AnyObject用於在Swift中替換Object-C的id型別;
Swift中編譯器不會對 AnyObject 例項的方法呼叫做出檢查,甚至對於 AnyObject 的所有方法呼叫都會返回 Optional 的結果。因此使用AnyObject例項之前我們務必需要檢測物件是否存在和型別有效性。
參考:ANY 和 ANYOBJECT

nil:
Swift中的nil與Object-C的nil是不一樣的。Object-C中,nil是一個指標指向一個不存在的object。在Swift中,nil不是指標,而是對於特定型別的預設值。任何optional型別都可以設定為nil,而不僅僅是物件型別;
optional型別變數的值預設就是nil;

10.typedef與typealias

typealias相當於Object-C中的typedef,用來為已經存在的型別重新定義名字。通過命名,可以使程式碼變得更加清晰。
參考:swift-typealias,typelias

//重新命名閉包,和Object-C一樣
typealias AsyncLoaderTask = (_:AnyObject?) ->Void
func submit(_ block:AsyncLoaderTask?,completion:AsyncLoaderTask?) -> Void {
    if let blk=block {
        self.queue.async(execute:{
            blk(nil)
            if let cmp=completion {
                cmp(nil)
            }
        })
    }
}

//protocol組合
protocol changeName{
    func changeNameTo(name:String)
}
protocol changeSex{
    func changeSexTo(sex:String)
}
typealias changeProtocol = protocol<changeName,changeSex>
struct Person:changeProtocol{
  func changeNameTo(name:String){
  }
  func changeSexTo(sex:SEX){
  }
}

//swift中很多型別別名即是typealias定義的
public typealias AnyClass = AnyObject.Type
public typealias NSInteger = Int

11.巨集定義

很抱歉,Swift中終於不支援巨集定義了。
Object-C中的巨集暴露到Swift中的話,只有簡單展開巨集會被直接變為同名常量。
參考:converting-complex-objective-c-macros-swift-functions,Objective的巨集到swift

//Object-C
#defien MTT_DEBUG 1

//Swift
let MTT_DEBUG = 1 
//以上二者等價

其餘複雜的巨集定義以及巨集巢狀都無法被翻譯到Swift裡,也無法在Swift中呼叫或使用。
所以要使用巨集,只能在Object-C中使用.

12.混合程式設計

處於某些目的,我們還是有必要在Swift程式碼中使用Object-C,或者在Object-C程式碼中裝一下樣子,搞點Swift。那這時就涉及到混合程式設計了。
參考:混和程式設計

1.OC中使用Swift
在工程的 Build Settings 中把 defines module 設為 YES.即可.(如需必要,再把把 product module name 設定為專案工程的名字。理論上不需要了,XCode 8.0已經預設是這樣了)
最後一步,在你的OC檔案中匯入 ProjectName-Swift.h.即(productModuleName-Swift.h)

2.Swift中使用OC
Swift程式碼引用OC,需依靠 Objective-C bridging header 將相關檔案暴露給Swift。
建立 Objective-C bridging header 有兩種方法

a.當你在Swift專案中嘗試建立OC檔案時,系統會自動幫你建立 Objective-C bridging header
b.自己建立 Objective-C bridging header
File > New > File > (iOS or OS X) > Source > Header File
切記,名字 一定要 是 專案工程名-Bridging-Header.
然後還有一步,在專案的 Build Settings 選項裡,要確保Swift Compiler 選項裡有這個 Bridging Header 檔案的設定,路徑必須指向檔案本身,而不是目錄!

13.API設計與函式命名對比

篇幅原因,這裡就不copy&&paste了,大家可以自行看原文或者譯文。這是一篇很好的API設計指導,終於可以拋棄Object-C的冗長命名了。
參考:Swift API設計 , 譯文


參考資料

結語

如果你在一門語言上,可以看到很多讓你曾痴狂,壓抑,熱愛,也失望的特性時。那就一定是Swift了。
本文比較了Swift3.0和Object-C在開發中主要的一些差別,限於篇幅(已經很長了)與作者本人認知能力,並未對Swift中更多特性做介紹或深究。如有疏漏或錯誤還望指正。
Swift就像一個四不像,因為var而像javascript,因為@objc而又保留了Object-C特性,因為內部類而多了點Java的影子,因為deinit而帶了些c++的氣味。但無論如何,Swift必定是將來!
參考:About Swift

相關文章