示例代码来源于 《iOS 11 Programming Fundamentals with Swift》

变量

Define and Call函数初始化

使用Define and Call函数进行变量初始化,Define and Call函数内容在下节。

let timed : Bool = {    
    if val == 1 {        
        return true    
    } else {        
        return false    
    }
}()

懒惰初始化

使用lazy关键字可以让变量在使用的时候再初始化。

  • Global的变量是自动懒惰初始化的,不需要lazy关键字。
  • Static的变量也是自动懒惰初始化的,不需要lazy关键字。
  • Property变量用关键字lazy进行懒惰初始化。
class MyView : UIView {
    lazy var arrow = self.arrowImage()
    func arrowImage () -> UIImage {
        // ... big image-generating code goes here ...
    }
}

如果没有lazy关键字,是不能通过编译的,因为MyView还没有初始化,不能调用实例方法。

一个比较常用的技术手段是结合使用define and call的函数来初始化lazy的成员变量:

lazy var prog : UIProgressView = {    
    let p = UIProgressView(progressViewStyle: .default)    
    p.alpha = 0.7    
    p.trackTintColor = UIColor.clear    
    p.progresstintColor = UIColor.black    
    p.frame = CGRect(x:0,y:0,width:self.view.bounds.size.width,height:20)    
    p.progress = 1.0    
    return p
}()
  • lazy的成员变量初始化不是线程安全的。
  • lazy的成员变量不能是只读的。
  • lazy的成员变量也不能加观察者。

计算变量

无论是类成员还是全局变量,都可以定义成计算变量,就是在使用的时候计算一个值出来,而不是保存这个值。也就是getter和setter。

var Now : String {     
    get {         
        return Date().description     
    }    
    set {         
        print(newValue) 
    }
}
  • 计算变量必须是var的,必须指定类型。
  • getter函数必须返回同类型的值。
  • setter函数中的newValue就是被赋予的值。也可以使用set(val)指定。
  • 如果没有setter函数,变量是只读的。
  • 必须有getter
  • 没有setter的时候可以简写
var Now : String {    
    return Date().description

}

变量观察者

和OC的KVO类似。

var s = "whatever" {     
    willSet {         
        print(newValue)     
    }    
    didSet {         
        print(oldValue)         
        // self.s = "something else" 
    }
}
  • 在变量被改变之前willSet会被调用,newValue表示新的值
  • 在变量被改变之后didSet会被调用,oldValue表示原来的值
  • 在初始化的时候和didSet中不会触发willSet和didSet
  • 如果变量是计算变量(有setter)则不能用willSet和didSet,因为可以在setter中做这些事情。

函数

函数嵌套

Swift中可以在函数体中定义函数,如果一个函数B只被函数A使用,那可以将B函数定义在函数An内。

func checkPair(_ p1:Piece,and p2:Piece) -> Path? {
    // ...
    if arr.count > 0 {
        func distance(_ pt1:Point,_ pt2:Point) -> Double { 1
            // utility to learn physical distance between two points
            let deltax = pt1.0 - pt2.0
            let deltay = pt1.1 - pt2.1
            return Double(deltax * deltax + deltay * deltay).squareRoot()
        }
        for thisPath in arr {
            var thisLength = 0.0
            for ix in thisPath.indices.dropLast() {
                thisLength += distance(thisPath[ix],thisPath[ix+1]) 2
            }
            // ...
        }
    }
    // ...
}

函数作为参数和返回值

因为Swift中函数是对象,所以函数也是可以作为参数传递的。

func doThis(_ f:() -> ()) { f() } func whatTodo() { print("I did it") } doThis(whatTodo)

同样,函数可以作为函数的返回值。函数作为参数或者返回值的一个好处是可以增加间接性,使得在调用的时候不必知道函数的定义,只要知道函数的签名就行。

一个比较实用的例子:

let size = CGSize(width:45,height:20)
UIGraphicsBeginImageContextWithOptions(size,false,0) 
let p = UIBezierPath(
    roundedRect: CGRect(x:0,y:0,width:45,height:20),cornerRadius: 8)
p.stroke() 
let result = UIGraphicsGetimageFromCurrentimageContext()! 
UIGraphicsEndImageContext()

上边这段代码是创建一个矩形的图片。这段代码中和UIGraphXXX相关的部分是可以提取的,因为每次画图都一样。定义一个函数:

func imageOfSize(_ size:CGSize,_ whatToDraw:() -> ()) -> UIImage {
    UIGraphicsBeginImageContextWithOptions(size,0)
    whatToDraw()
    let result = UIGraphicsGetimageFromCurrentimageContext()!
    UIGraphicsEndImageContext()
    return result
}

上边的函数就是纯粹的图片制作过程,将画图的部分“外包”了出去,调用者将画图部分的函数传入即可。

使用代码:

func drawing() { 
    let p = UIBezierPath( roundedRect: CGRect(x:0,y:0,width:45,height:20),cornerRadius: 8) 
    p.stroke() 
} 

let image = imageOfSize(CGSize(width:45,drawing)

iOS 10已经有一个函数叫做UIGraphicsBeginImageContext和imageOfSize提供差不多的功能。

函数作为返回值可以引出一个模式,类似于微型的工厂模式:

func makeRoundedRectangleMaker(_ sz:CGSize) -> () -> UIImage {
    func f () -> UIImage { 
        let im = imageOfSize(sz) { 
            let p = UIBezierPath(roundedRect: CGRect(origin:CGPoint.zero,size:sz),cornerRadius: 8) 

            p.stroke() 
        } 

        return im
    } 

    return f
}

let maker = makeRoundedRectangleMaker(CGSize(width:45,height:20)) 
self.iv.image = maker()

上述代码返回一个函数,这个函数是一个ImageMaker,调用maker能生成一个Image,这个Image的大小是制作Maker时候决定的,也就是makeRoundedRectangleMaker的参数sz。

匿名函数

UIView.animate(withDuration:0.4,animations: {        
        () -> () in        
        self.myButton.frame.origin.y += 20   
    },completion: {        
        (finished:Bool) -> () in        
        print("finished: \(finished)")    

    })

匿名函数很像OC中的block,说明下上边的代码。

UIView.animate(withDuration: animations: completion:)中的aimations参数是一个()->Void类型的函数,compeltion是一个(Bool)->Void类型的函数。使用者可以定义两个这样类型的函数然后传入,也可以在调用的原位定义一个匿名函数。() -> () in 这行代码的意思是下边的函数是一个() -> ()类型。匿名函数的参数和返回值写在了大括号的第一行。

匿名函数有很多书写的简化规则,看到能读懂就行,不必非得使用这些语法糖。

  • 如果返回值编译器能够知道,那么就可以省略返回值,如果没有参数也可以省略参数,如果返回值和参数都能省略,那么in也可以省略,所以,上边的animations参数第一行是可以省略的:
UIView.animate(withDuration:0.4,animations: { 
        // * (no in line)        
        self.myButton.frame.origin.y += 20    

    },completion: { 
        (finished:Bool) in        
        print("finished: \(finished)")

    })

同样,在completion参数中省略的返回值。

  • 对于completion来说,参数类型也可以省略,因为参数类型在UIView.animate函数的参数中已经标明了,所以可以写成:
UIView.animate(withDuration:0.4,completion: { 
        finished in        
        print("finished: \(finished)")

    })
  • 更进一步,参数的名字也可以使用 0, 1…取代,这样参数的名字也可以省略:
UIView.animate(withDuration:0.4,animations: { 

        self.myButton.frame.origin.y += 20    

    },completion: { 
        print("finished: \($0)")

    })
  • 还有一种常见的变体要认识:
UIView.animate(withDuration:0.4,animations: { 

        self.myButton.frame.origin.y += 20    

    }) { 
        print("finished: \($0)")
    }

当匿名函数是函数的最后一个参数的时候,可以将匿名函数的大括号部分写在函数调用之后。

有了匿名函数,上边画图的代码可以写在一起:

let image = imageOfSize(CGSize(width:45,height:20),{ 
    () -> () in 
    let p = UIBezierPath( roundedRect: CGRect(x:0,y:0,width:45,cornerRadius: 8) 
    p.stroke() 

})

Define and Call

就是定义函数的同时调用函数,这种形式:

{
    // ... code goes here
}()

和匿名函数相似,不同的是,匿名函数提供的是一个过程,Define and Call提供的是一个过程的结果,例子:

content.addAttribute(
    .paragraphStyle,value: {
        let para = NSMutableParagraphStyle()
        para.headindent = 10
        para.firstLineHeadindent = 10
        // ... more configuration of para ...
        return para
    }(),range:NSRange(location:0,length:1))

其中value参数是通过一个Define and Call的函数算出的结果。这种一次性的函数适合使用Define and Call或者嵌套函数。有两个好处,一个是可见的范围缩小了,避免冲突和误用,二是可以捕获上下文的变量,不需要定义一系列参数传递。

函数装饰器 & escaping

看一个函数:

func countAdder(_ f: @escaping () -> ()) -> () -> () { 
    var ct = 0 

    return {
        ct = ct + 1 
        print("count is \(ct)") 
        f()
    }
}

这个函数接受一个()->()函数,返回一个()->()函数。作用是调用传入的函数f,并且记录打印函数f调用的次数。

使用:

func greet () { 
    print("howdy") 
} 

let countedGreet = countAdder(greet) 

countedGreet() //count is 1,howdy
countedGreet() //count is 2,howdy
countedGreet() //count is 3,howdy

内部的匿名函数捕获了临时变量ct,并保存下来,以后的每次调用都会增加ct,并维护这个结果。从结果上看countAdder给greet会这样签名的函数增加了方法,类似一个微型的装饰器模型。

@escaping,@escaping的意思是这个函数参数在当前函数执行完成后还可能被调用,在上边的例子中,f随着匿名函数被返回,在后续的调用中执行。这种情况下,f参数需要被标记为@escaping,否则会有编译错误。因为使用者传入的参数f可能是一个闭包,可能捕获了一些临时变量,那么标记为@escaping意思就是在函数执行完之后也要保留这些捕获的变量。

Curried Functions

还使用画图的例子,上面的画图例子的圆角半径为8是写死的,如果不想写死,代码应该这样写:

func makeRoundedRectangleMaker(_ sz:CGSize,_ r:CGFloat) -> () -> UIImage { 
    return { 
        imageOfSize(sz) { 
            let p = UIBezierPath( roundedRect: CGRect(origin:CGPoint.zero,cornerRadius: r) 

            p.stroke()
        }
    }
}

let maker = makeRoundedRectangleMaker(CGSize(width:45,8)

这里还有另外一种思路:

func makeRoundedRectangleMaker(_ sz:CGSize) -> (CGFloat) -> UIImage { 
    return { 
        r in
        imageOfSize(sz) { 
            let p = UIBezierPath( roundedRect: CGRect(origin:CGPoint.zero,height:20))
self.iv.image = maker(8)

这段代码的不同之处在于makeRoundedRectangleMaker接受的还是一个参数,但是返回的函数多了一个参数CGFloat。闭包内同样也捕获了返回函数的参数r,在使用的时候再提供r的值。这种方式就叫做Curried Function。

继承 & 多态

  • Swift中只有Class可以继承
  • Swift是单继承的。
  • 没有一个像OC中NSObject这样的公共基类。
  • 可以将一个class标记为final,这样这个class就不能被继承。

继承的写法:

// Cat是Kitten的基类

class Dog {
    func barkAt(cat:Kitten) {}
}

class NoisyDog : Dog {
    override func barkAt(cat:Cat) {} // or Cat?
}
  • 子类重写基类的方法需要使用override关键字。
  • override的方法的参数可以是基类的参数的基类,也可以是基类参数对应的可选类型。这一要求是保证里式替换原则的。

子类的初始化

初始化函数的继承

  • 如果子类没有定义初始化函数,那么所有的初始化函数继承自基类。(子类可以没有初始化函数的必要条件是使用基类的函数就能把所有的成员都初始化了)
  • 如果子类没有designated initializer,可以定义自己的convenience initializer,继承基类的designated initializer。
  • 如果子类定义了自己的designated initializer,那么所有基类的designated initializer和convenience initializer都不会被继承了。
  • 如果子类重写了基类所有的designated initializer,那么基类的convenience initializer又可以被子类继承了。

上述规则可以这样理解,如果基类的初始化函数能满足子类的初始化需求,那么子类可以不写初始化函数,使用基类的。另外,子类也可以定义一些辅助函数(convenience initializer)来使用基类的designated initializer初始化。如果子类定义了自己的designated initializer 那么很可能的情况是基类的designated initializer已经不能满足子类的初始化需求了,那么所有的基类的designated initializer就不能成为子类的初始化函数了。(但是在子类的designated initializer中必须调用基类的designated initializer)。因为基类的convenience initializer中肯定是调用了基类的designated initializer,所以子类如果重写了所有的designated initializer,那么基类的convenience initializer中的调用就变成了调用子类的designated initializer,所以又可以初始化子类了,所以就可以继承了。

  • 重写基类的convenience initializer,不需要override关键字,重写基类的designated initializer需要使用override关键字。
  • 基类的初始化函数可以被重写成failable的初始化函数(就是返回?类型的实例的初始化函数),但是反过来不行。

required 初始化函数

在基类中被标记为required的初始化函数,子类必须也有这个初始化函数。“必须有”的意思是:如果子类不能根据上述规则通过继承拥有这个初始化函数,就必须重写它。

这条规则的使用场景:

func dogMakerAndNamer(_ whattype:Dog.Type) -> Dog {
    let d = whattype.init(name:"Fido") // compile error
    return d
}

dogMakerAndNamer是一个工厂函数,根据传入的dog的具体类型来创建一个dog出来,那么如果子类没有init(name:)这个初始化函数,这个工厂函数就行不通了。解决的办法就是在基类Dog的init(name:)前边加上required关键字,要求所有子类都实现这个初始化函数。

class Dog {
    var name : String
    required init(name:String) {
        self.name = name
    }
}

属性和方法的重写

属性分为两类,一类是普通变量属性,一类是计算变量属性。

class Dog {
    var name : String   //普通变量属性
    var age : Float {   //计算变量属性,只读
        return 1 + 10
    }

    required init(name:String) {
        self.name = name
    }
}

子类可以使用计算变量重写基类的普通变量属性,但是不能改变访问权限

class NoisyDog : Dog {
    override var name: String{
        set { self.name = newValue } //如果注释掉这一句,编译错误
        get { return "dog" }
    }
}

子类可以重写基类的计算变量的属性,并且可以改变访问权限

class NoisyDog : Dog {
    override var age: Float{
        set { self.age = newValue } //可以添加setter
        get { return 20 }
    }
}

向下类型转换

let d : Dog? = NoisyDog()
let d2 = d as? NoisyDog
d2?.beQuiet()

类型判断

let d : Dog? = NoisyDog()

if d is NoisyDog {
    let d2 = d as! NoisyDog
    d2.beQuiet()
}

上述判断和转化代码可以合并成一句:

if let d2 = d as? NoisyDog{
    print(d2)
}

Protocol

Swift中一个类实现一个Protocol需要显式声明。Protocol可以被struct或者enum实现。

protocol Flier { func fly() }
  • 一个类可以实现多个Protocol。
  • protocol中可以定义类属性,使用static关键字。
  • 如果Protocol 中的一个方法可能会改变属性,并且这个Protocol设计允许被struct或者enum实现,那么这个方法应该使用mutating关键字。
  • Protocol相当于提供了另一个维度的继承体系,所以类型转换和继承体系的语法相同。

protocol的组合

如果一个函数接受一个参数,要求这个参数实现两个Protocol,可以使用Protocol的组合

func f(_ x: customstringconvertible & CustomDebugStringConvertible) { }

protocol和class也可以组合

protocol MyViewProtocol : class {
    func doSomethingCool()
}

class ViewController: UIViewController {
    var v: (UIView & MyViewProtocol)?
    // ...
}

ViewController 的构造函数接受一个参数,要求这个参数是UIView类型,并且实现了MyViewProtocol协议。

protocol MyViewProtocol : class,class关键字表示只能被class类型的实现。

Optional Protocol Method

是为了和OC 的Protocol兼容。必须标记为@objc,并且标记了@objc的protocol只能被class类型实现,不能被struct和enum实现。

@objc protocol Flier {
    @objc optional var song : String {get}
    @objc optional func sing()
}


class Bird : Flier {
    func sing() { 
        print("tweet")
    }
}

Bird只实现了一个sing方法。在这种情况下,Swift不知道song属性是不是安全的。

let f : Flier = Bird()
let s = f.song // s is an Optional wrapping a String

s将是一个String?而不是String,如果协议中song本身就是String?那么s将是一个String??。这一点对于方法的返回值也适用。

@objc protocol Flier { 
    @objc optional var song : String? {get} 
    @objc optional func sing() } let f : Flier = Bird() let s = f.song // s is an Optional wrapping an Optional wrapping a String

对于方法的调用需要先拆箱

let f : Flier = Bird() 
f.sing?()

Literal Convertibles

Swift中字面变量的转换是靠实现了一个或者多个协议来实现的。

struct nest : ExpressibleByIntegerLiteral { 
    var eggCount : Int = 0 

    init() {} 

    init(integerLiteral val: Int) { 
        self.eggCount = val 
    } 
}

nest实现了ExpressibleByIntegerLiteral协议,就可以使用Integer的字面变量来表示一个nest,比如有这样一个函数:

func reportEggs(_ nest:nest) { 
    print("this nest contains \(nest.eggCount) eggs") 
}

接受的是一个nest参数,就可以传入一个字面变量

reportEggs(4) // this nest contains 4 eggs

ExpressibleByIntegerLiteral,这个协议要求实现init(integerLiteral:)方法。nest实现了。

注意,这个协议是关于字面变量的,并不是Int类型到nest的转换。下边代码是不能通过编译的:

var x = 4 reportEggs(x)

其他的字面变量协议有:
- ExpressibleByNilLiteral
- ExpressibleByBooleanLiteral
- ExpressibleByIntegerLiteral
- ExpressibleByFloatLiteral
- ExpressibleByStringLiteral
- ExpressibleByExtendedGraphemeClusterLiteral
- ExpressibleByUnicodeScalarLiteral
- ExpressibleByArrayLiteral
- ExpressibleByDictionaryLiteral

泛型

类型是对象实例的模板,泛型就是类型的模板。不同的是,对象的的实例化是在运行期间进行的,模板的实例化是在编译期间进行的。

泛型函数

func dogMakerAndNamer<WhatType:Dog>(_:WhatType.Type) -> WhatType { 
    let d = WhatType.init(name:"Fido") 
    return d 
}

在这个函数中,WhatType是一个占位符,在编译的时候会替换成真正的类型。对这个类型的要求是继承自Dog.

Optional就是通过泛型来实现的,它是一个泛型的enum。

enum Optional<Wrapped> { 
   case none 
   case some(Wrapped)
   init(_ some: Wrapped) // ...
}

泛型协议

Self

如果一个protocol的方法声明中使用了Self,这个Self表示实现这个protocol的那个类的当前实例本身。相当于在类的实现中的self。也就是说Self是protocol中的self(因为此时不知道具体类型是啥)。

associated type

protocol Flier { 
    associatedtype Other 
    func flockTogetherWith(_ f:Other) 
    func mateWith(_ f:Other) 
}

一个具体的类实现这个protocol的时候需要将Other的部分替换成具体的类型:

struct Bird : Flier { 
    func flockTogetherWith(_ f:Bird) {} 
    func mateWith(_ f:Bird) {} 
}

上边的字面变量协议就是泛型协议。

public protocol ExpressibleByIntegerLiteral {
    associatedtype IntegerLiteralType
    public init(integerLiteral value: Self.IntegerLiteralType)
}

泛型类

struct HolderOfTwoSameThings<T> {
    var firstThing : T 
    var secondThing : T 

    init(thingOne:T,thingTwo:T) {
        self.firstThing = thingOne
        self.secondThing = thingTwo 
    }
}

let holder = HolderOfTwoSameThings(thingOne:"howdy",thingTwo:"getLost")

可以使用多个类型的泛型

func flockTwoTogether<T,U>(_ f1:T,_ f2:U) {}

泛型的约束

protocol Flier {
    func fly()
}

protocol Flocker {
    associatedtype Other : Flier // *
    func flockTogetherWith(f:Other)
}

struct Bird : Flocker,Flier {
    func fly() {}
    func flockTogetherWith(f:Bird) {}
}

Flocker定义了一个protocol,它的associatedtype要求必须是一个Flier,所以Bird要想采用Flocker协议,就必须也采用Flier协议。

显示的实例化泛型

protocol Flier { associatedtype Other } 

struct Bird : Flier { typealias Other = String }

或者

class Dog<T> { 
    var name : T?
} 

let d = Dog<String>()

组合使用泛型协议和泛型对象

protocol Flier { init() } struct Bird : Flier {
    init() {} 
} 

struct FlierMaker<T:Flier> {
    static func makeFlier() -> T {
        return T()
    } 
} 

let f = FlierMaker<Bird>.makeFlier() // returns a Bird

泛型的继承

class Dog<T> { 
    func speak(_ what:T) {} 
}

class NoisyDog<T> : Dog<T> {}

或者在继承的时候就实例化

class NoisyDog : Dog<String> { 
    override func speak(_ what:String) {} 
}

Associated Type Chains

protocol fighter { 
    associatedtype Enemy where Enemy : fighter 
}

Enemy 占位符要求也是一个fighter,因为Enemy本身是在fighter中使用的,所以需要使用where语句。

实例化

struct Soldier : fighter {
    typealias Enemy = Archer 
} 

struct Archer : fighter {
    typealias Enemy = Soldier
}
struct Camp<T:fighter> { 
    var spy : T.Enemy?
}

spy这个变量的类型是fighter的Enemy,但是因为fighter也还没确定,所以使用T.Enemy。

Enemy是一个占位符,fighter是一个泛型。在Camp中,T也是占位符,代表一个实现了fighter的类型。所以spy是一个<占位符>.<占位符>的形式。随后的实例化的时候,会替换所有的占位符。

var c = Camp<Soldier>() c.spy = Archer()

泛型约束的where语句

func flyAndWalk<T: Flier> (_ f:T) {}
func flyAndWalk<T> (_ f:T) where T: Flier {}

func flyAndWalk2<T: Flier & Walker> (_ f:T) {}
func flyAndWalk2<T> (_ f:T) where T: Flier & Walker {} 
func flyAndWalk2a<T> (_ f:T) where T: Flier,T: Walker {}

更复杂些的约束

protocol Flier { associatedtype Other } 

struct Bird : Flier { typealias Other = String } 

struct Insect : Flier { typealias Other = Bird } 

func flockTogether<T> (_ f:T) where T:Flier,T.Other:Equatable {}

flockTogether有一个类型占位符T,where语句中要求T是Flier的,因为Flier本身有一个占位符Other,还可以要求这个Other是可比较的。于是使用:T.Other:Equatable。

flockTogether(Bird()) // okay 
flockTogether(Insect()) // compile error

因为Insect的Other是Bird,而Bird不是Equatable的。所以第二句编译错误。

如果使用”==”操作符,意思是类型必须是后边的类型。

func flockTogether<T> (_ f:T) where T:Flier,T.Other == Walker {}

T.Other必须是Walker类型,不能是subclass。

Swfit中append(contentsOf:)函数的实现就使用了==操作符

mutating func append<S>(contentsOf newElements: S) 
    where S:Sequence,S.Element == Character

Extensions

Extensions类似于OC中的Category,能给已有的类添加功能。

  • extension不能重写原来的方法,但是可以进行函数重载
  • extension不能添加stored property,但是可以添加compute property,可以添加static或者class的属性,无论是不是计算属性都可以
  • extension不能添加designated initializer但是可以添加convenience initializer。
extension Array {
    mutating func shuffle () { 
        for i in (0..<self.count).reversed() { 
            let ix1 = i 
            let ix2 = Int(arc4random_uniform(UInt32(i+1))) 
            self.swapAt(ix1,ix2) 
        } 
    }
}

如果扩展一个protocol,就可以添加属性,还可以提供方法的实现

protocol Flier { } extension Flier {
    func fly() {
        print("flap flap flap")
    } 
} 

struct Bird : Flier { }

Bird继承了fly的实现。但是这种继承的方法没有多态的特性

let i = Insect() 
i.fly() // whirr

let f : Flier = Insect() 
f.fly() // flap flap flap (!!)

如果仍想要多态的特性,需要在原来的protocol中加上fly方法

protocol Flier { 
    func fly() // * 

} 

extension Flier { 
    func fly() { 
        print("flap flap flap") 
    } 
}

也可以给泛型添加extension,如,使用extension给Array添加一个求最小值的方法

extension Array where Element:Comparable { 
    func myMin() -> Element { 
        var minimum = self[0] 
        for ix in 1..<self.count { 
            if self[ix] < minimum { 
                minimum = self[ix] 
            } 
        } 
        return minimum
    }
}

let a = [1,4,5,0,8]
print(a.myMin())

myMin这个函数只有在Comparable对象实例化的Array中才有(Array本身是泛型,初始化的时候会将泛型实例化),如果Array中保存的对象不是Comparable的,那么就不会看见myMin这个函数

let b = [UIColor.blue,UIColor.red]
b.myMin() // 编译错误

Umbrella Types

Any
Any是Swift中的通用类型,但需要一个Any对象的时候,可以使用任何的类型。和OC交互的时候,OC中的id类型都可以表示为Any。比如UserDefault,NSCoding中的参数。

let ud = UserDefaults.standard 
ud.set("howdy",forKey:"greeting") 
ud.set(Date(),forKey:"Now")

取出来使用的需要进行类型转换:

let ud = UserDefaults.standard 
let d = ud.object(forKey:"Now") as? Date 
if d != nil { // ...

}

AnyObject
AnyObject是一个空protocol,所有的类型都自动实现了这个protocol。在Swift中Any是可以代表任何类型,AnyObject代表任何对象类型,Swift中任何类型都是对象类型,所以没有区别,但是OC中不是,所以AnyObject等价于OC中的id。

可以看一下类型转换的过程

let s = "howdy" as AnyObject // String to Nsstring to AnyObject 
let i = 1 as AnyObject // Int to NSNumber to AnyObject 
let r = CGRect() as AnyObject // CGRect to NSValue to AnyObject 
let d = Date() as AnyObject // Date to NSDate to AnyObject 
let b = Bird() as AnyObject // Bird (struct) to Boxed type to AnyObject

AnyObject的使用也和id类似:

class Dog { 
    @objc var noise : String = "woof" 
    @objc func bark() -> String { 
        return "woof" 
    } 
} 

class Cat {}

let c : AnyObject = Cat() 
let s = c.noise

let s = c.noise这句话是可以编译过的,等价于OC中的[c noise],向一个id发送消息,在编译期间并不检查。

如果调用的时候加一个?号,那么即使实际的类型没有实现方法,也不会crash,但是返回的值是String?

let c : AnyObject = Cat() 
let s = c.bark?()

能这样用的前提条件是方法或者属性标记为@objc,或者类本身就是NSObject的子类。

AnyClass
AnyClass是AnyObject的子类,对应OC中的Class。

class Dog { 
    @objc static var whatADogSays : String = "woof" 

} 

class Cat {}

let c : AnyClass = Cat.self 
let s = c.whatADogSays

Swift4 学习笔记——高级篇的更多相关文章

  1. three.js模拟实现太阳系行星体系功能

    这篇文章主要介绍了three.js模拟实现太阳系行星体系功能,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下

  2. HTML5 WebSocket实现点对点聊天的示例代码

    这篇文章主要介绍了HTML5 WebSocket实现点对点聊天的示例代码的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. HTML5页面无缝闪开的问题及解决方案

    这篇文章主要介绍了HTML5页面无缝闪开方案,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  4. ios – 在Swift的UIView中找到UILabel

    我正在尝试在我的UIViewControllers的超级视图中找到我的UILabels.这是我的代码:这是在Objective-C中推荐的方式,但是在Swift中我只得到UIViews和CALayer.我肯定在提供给这个方法的视图中有UILabel.我错过了什么?我的UIViewController中的调用:解决方法使用函数式编程概念可以更轻松地实现这一目标.

  5. ios – 仅在异步函数完成执行后运行代码

    所以,例如:如果问题是你不知道要调用什么函数,你可以配置你周围的函数/对象,这样有人可以给你一个函数,然后你在我上面说“调用函数”的地方调用你的函数.例如:

  6. ios – 在Swift中将输入字段字符串转换为Int

    所以我非常擅长制作APP广告Swift,我试图在文本字段中做一些非常简单的输入,取值,然后将它们用作Int进行某些计算.但是’vardistance’有些东西不正确它是导致错误的最后一行代码.它说致命错误:无法解开Optional.None解决方法在你的例子中,距离是一个Int?否则称为可选的Int..toInt()返回Int?因为从String到Int的转换可能失败.请参阅以下示例:

  7. 如何在iOS中检测文本(字符串)语言?

    例如,给定以下字符串:我想检测每个声明的字符串中使用的语言.让我们假设已实现函数的签名是:如果没有检测到语言,则返回可选字符串.因此,适当的结果将是:有一个简单的方法来实现它吗?

  8. ios – 如何使用Objective C类中的多个参数调用Swift函数?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  9. xamarin – 崩溃在AccountStore.Create().保存(e.Account,“);

    在Xamarin.Forms示例TodoAwsAuth中https://developer.xamarin.com/guides/xamarin-forms/web-services/authentication/oauth/成功登录后,在aOnAuthenticationCompleted事件中,应用程序在尝试保存到Xamarin.Auth时崩溃错误说不能对钥匙串说期待着寻求帮助.解决方法看看你

  10. ios – 将视频分享到Facebook

    我正在编写一个简单的测试应用程序,用于将视频从iOS上传到Facebook.由于FacebookSDK的所有文档都在Objective-C中,因此我发现很难在线找到有关如何使用Swift执行此操作的示例/教程.到目前为止我有这个在我的UI上放置一个共享按钮,但它看起来已禁用,从我读到的这是因为没有内容设置,但我看不出这是怎么可能的.我的getVideoURL()函数返回一个NSURL,它肯定包含视

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部