从Object-C -> Swift3.0

前言

当我们开始接触一门新语言时,我们难免避免不了类型和基本语法规则。我们会急切的想知道如何用新的语法规则写我们原先所熟知的语句。本文旨在对于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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容