Swift與OC交互

為了方面查閱特轉(zhuǎn)一篇文章

Swift的設(shè)計(jì)的初衷就是擺脫ObjC沉重的歷史包袱片任,畢竟ObjC的歷史太過悠久磷斧,相比于很多現(xiàn)代化語言它缺少一些很酷的語法特性遣鼓,而且ObjC的語法和其他語言相比差別很大诀蓉。但是Apple同時(shí)也不能忽視ObjC的地位住诸,畢竟ObjC經(jīng)過二十多年的歷史積累了大量的資源(開發(fā)者、框架箱亿、類庫等)跛锌,因此在Swift推出的初期必須考慮兼容ObjC。但同時(shí)Swift和ObjC是基于兩種不同的方式來實(shí)現(xiàn)的(例如ObjC可以在運(yùn)行時(shí)決定對(duì)象類型届惋,但是Swift為了提高效率要求在編譯時(shí)就必須確定對(duì)象類型)髓帽,所以要無縫兼容需要做大量的工作。而作為開發(fā)人員我們有必要了解兩種語言之間的轉(zhuǎn)化關(guān)系才能對(duì)Swift有更深刻的理解脑豹。

Swift與OC之間的映射

其實(shí)從前面的例子中大家不難發(fā)現(xiàn)Swift和ObjC必然存在著一定的映射關(guān)系郑藏,例如對(duì)于文件的操作使用了字符串的writeToFile方法,在網(wǎng)絡(luò)請(qǐng)求時(shí)使用的NSURLSession瘩欺,雖然調(diào)用方式不同但是其參數(shù)完全和做ObjC開發(fā)時(shí)調(diào)用方式一致必盖。原因就是Swift編譯器自動(dòng)做了映射,下面列舉了部分Swift和ObjC的映射關(guān)系幫助大家理解:

|Swift|Objc|備注|
|::|::|::|
|AnyObject|id|Swift中用到Objc中id類型的參數(shù)被標(biāo)記為對(duì)應(yīng)的可選類型|
|Array俱饿、Dictionary歌粥、Set|NSArray、NSDictionary拍埠、NSSet|Swift中的Int失驶、UInt、Float枣购、Double嬉探、Bool會(huì)自動(dòng)橋接為NSNumber|
|Int|NSInteger、NSUInteger|其他基本類型情況類似棉圈,不再一一列舉|
|NSObjectProtocol|NSObject協(xié)議(注意不是NSObject類)|由于Swift在繼承或者實(shí)現(xiàn)時(shí)沒有類的命名空間的概念涩堤,而ObjC中既有NSObject類又有NSObject協(xié)議,所以在Swift中將NSObject協(xié)議對(duì)應(yīng)成了NSObjectProtocol|
|CGContext|CGContextRef|Core Foundation中其他情況均是如此分瘾,由于Swift本身就是引用類型定躏,在Swift不需要再加上“Ref”|
|ErrorType|NSError||
|“ab:"|@selector(ab:)|Swift可以自動(dòng)將字符串轉(zhuǎn)化成成selector|
|@NSCopying|copy屬性|init(x:X,y:Y) initWithX:(X)x y:(Y)y 構(gòu)造方法映射,Swift會(huì)去掉“With”并且第一個(gè)字母小寫作為其第一個(gè)參數(shù),同時(shí)也不需要調(diào)用alloc方法痊远,但是需要注意ObjC中的便利工廠方法(構(gòu)建對(duì)象的靜態(tài)方法)對(duì)應(yīng)成了Swift的便利構(gòu)造方法|
|func xY(a:A,b:B) |void xY:(A)a b:(B)b| |
|extension(擴(kuò)展) |category(分類) |注意:不能為ObjC中存在的方法進(jìn)行extension|
|Closure(閉包)| block(塊) |注意:Swift中的閉包可以直接修改外部變量垮抗,但是block中要修改外部變量必須聲明為__block|

Swift兼容大部分ObjC(通過類似上面的對(duì)應(yīng)關(guān)系),多數(shù)ObjC的功能在Swift中都能使用碧聪。當(dāng)然冒版,還是有個(gè)別地方Swift并沒有考慮兼容ObjC,例如:Swift中無法使用預(yù)處理指令(例如:宏定義逞姿,事實(shí)上在Swift中推舉使用常量定義)辞嗡;Swift中也無法使用performSelector來執(zhí)行一個(gè)方法,因?yàn)镾wift認(rèn)為這么做是不安全的滞造。

相反续室,如果在ObjC中使用Swift也同樣是可行的(除了個(gè)別Swift新增的高級(jí)功能)。Swift中如果一個(gè)類繼承于NSObject谒养,那么他會(huì)自動(dòng)和ObjC兼容挺狰,這樣ObjC就可以按照上面的對(duì)應(yīng)關(guān)系調(diào)用Swift的方法、屬性等买窟。但是如果Swift中的類沒有繼承于NSObject呢丰泊?此時(shí)就需要使用一個(gè)關(guān)鍵字“@objc”進(jìn)行標(biāo)注,ObjC就可以像使用正常的ObjC編碼一樣調(diào)用Swift了(事實(shí)上繼承于NSObject的類之所以在ObjC中能夠直接調(diào)用也是因?yàn)榫幾g器會(huì)自動(dòng)給類和非private成員添加上@objc始绍,類似的@IBoutlet瞳购、@IBAction、@NSManaged修飾的方法屬性Swift編譯器也會(huì)自動(dòng)添加@objc標(biāo)記)亏推。

Swift中調(diào)用C

由于ObjC是C的超集学赛,使得在ObjC可以無縫訪問C語言。但是Swift的產(chǎn)生就是ObjC without C吞杭,因此在Swift中不可能像在ObjC中混編入C一樣簡單盏浇。但是考慮到C語言的強(qiáng)大以及歷時(shí)那么多年留下了豐富的類庫,有時(shí)候又不得不使用它篇亭,Swift中還是保留了與一定數(shù)量的C語言類型和特性的兼容缠捌。前面介紹過關(guān)于如何在Swift中使用ObjC的知識(shí)锄贷,事實(shí)上在Swift中使用C也是類似的(因?yàn)镺bjC是C的超集译蒂,ObjC既然可以橋接,C自然也可以)谊却,你需要一個(gè)橋接文件柔昼,不同的是ObjC中的很多內(nèi)容在橋接到Swift時(shí)都是類似,很容易上手炎辨。例如ObjC中使用的NSObject捕透,在Swift中仍然對(duì)應(yīng)NSObject,很多時(shí)候開發(fā)人員感覺不到這種轉(zhuǎn)化,只是編程語言發(fā)生了變化乙嘀。但是C導(dǎo)入Swift就需要必須要了解具體的對(duì)應(yīng)關(guān)系:

|C類型|Swift類型 |說明|
|::|::|::|
|基本類型| | |
|char,signed |char CChar|類似的unsigned char對(duì)應(yīng)CUnsignedChar|
|int|CInt|類似的unsigned int對(duì)應(yīng)CUnsignedInt|
|short|CShort|類似的unsigned short對(duì)應(yīng)CUnsignedShort|
|long |CLong|類似的unsigned long對(duì)應(yīng)CUnsignedLong|
|long long |CLongLong|類似的unsigned long long 對(duì)應(yīng) CUnsignedLongLong|
|float |CFloat| |
|double|CDouble||
|構(gòu)造體類型| | 注意:結(jié)構(gòu)體實(shí)現(xiàn)|
|枚舉typedef NS_ENUM(NSInteger,A){AB,AC} |enum A:Int{case B,C}|去掉對(duì)應(yīng)的前綴 ,注意C中的NS_Options會(huì)對(duì)應(yīng)成Swift中實(shí)現(xiàn)OptionSetType的結(jié)構(gòu)體|
|結(jié)構(gòu)體| |對(duì)應(yīng)Swift中的結(jié)構(gòu)體 |
|聯(lián)合 | |Swift中不能完全支持聯(lián)合末购,建議使用枚舉關(guān)聯(lián)值代替|
|指針類型||C語言中的指針類型映射成了Swift中的泛型|
|Type *|UnsafeMutablePointer<Type> |作為返回類型、變量虎谢、參數(shù)類型時(shí)|
|const Type *|UnsafePointer<Type>| 作為返回類型盟榴、變量、參數(shù)類型時(shí)|
|Type *const *|UnsafePointer<Type>| 對(duì)于類類型|
|Type *__strong *|UnsafeMutablePointer<Type>|對(duì)于類類型|
|Type * *| AutoreleasingUnsafePointer<Type>|對(duì)于類類型|
|函數(shù)指針|閉包|||

對(duì)于其他類型的映射關(guān)系都很容易理解婴噩,這里主要說一下指針的內(nèi)容擎场。通過上表可以看到在C中定義的一些指針類型當(dāng)在Swift中使用時(shí)會(huì)有對(duì)應(yīng)的類型,但是如果一個(gè)參數(shù)為某種指針類型几莽,實(shí)際調(diào)用時(shí)應(yīng)該使用何種Swift數(shù)據(jù)類型的數(shù)據(jù)作為參數(shù)調(diào)用呢迅办?例如參數(shù)為UnsafePointer<Type>,是否只能傳入U(xiǎn)nsafePointer<Type>呢章蚣,其實(shí)也可以傳入nil站欺,并且最終調(diào)用時(shí)將會(huì)轉(zhuǎn)化為null指針來調(diào)用。下表列出了這種參數(shù)調(diào)用對(duì)應(yīng)關(guān)系:

|可用類型|最終轉(zhuǎn)化類型|
|::|::|
|UnsafePointer<Type> | 注意:如果Type為Void則可以代表任何類型|
| nil | null|
|UnsafePointer<Type>究驴、UnsafeMutablePointer<Type>镊绪、AutoreleasingUnsafeMutablePointer<Type>| UnsafePointer<Type>|
| String |如果Type為Int、或者Int8將最終轉(zhuǎn)化為UTF8字符串|
| &typeValue| 元素地址|
| Type類型的數(shù)組([typeValue1,typeValue2])| 數(shù)組首地址|
| UnsafeMutablePointer<Type> | 注意:如果Type為Void則可以代表任何類型|
| nil| null |
|UnsafeMutablePointer<Type> | UnsafeMutablePointer<Type> |
| &typeValue| 元素地址|
|Type類型的數(shù)組的地址(&[typeValue1,typeValue2]) |數(shù)組地址|
| AutoreleasingUnsafeMutablePointer<Type>||
| nil| null |
| AutoreleasingUnsafeMutablePointer<Type> | AutoreleasingUnsafeMutablePointer<Type>|
| &typeValue | 元素地址|
下面不妨看一下如何在Swift中使用C語言洒忧,假設(shè)現(xiàn)在有一個(gè)用于字符串拼接的C庫函數(shù)“stringAppend(char*,const char *)”蝴韭,將其對(duì)應(yīng)的文件導(dǎo)入到一個(gè)Swift項(xiàng)目中按照提示添加橋接頭文件并在橋接頭文件中引入對(duì)應(yīng)的C文件。
string.h

#ifndef __UseCInSwift__Common__
#define __UseCInSwift__Common__void stringAppend(char *source, char *toAppend)
 
#include
 
void stringAppend(char *source,const char *toAppend);
 
#endif

string.c

#include "string.h"
 
void stringAppend(char *source,const char *toAppend) {
    unsigned long sourceLen = strlen(source);
    char *pSource = source + sourceLen;
    const char *pAppend = toAppend;
    while (*pAppend != '\0') {
        *pSource++ = *pAppend++;
    }
}

UseCinSwift-Briding-Header.h

#import "string.h"

然后在Swift中調(diào)用上面的C函數(shù)

import Foundation
 
var sourceStr:String = "Hello"
var appendStr:String = ",World!"
 
var sourceCStr = (sourceStr as NSString).UTF8String
var sourceMutablePointer:UnsafeMutablePointer = UnsafeMutablePointer(sourceCStr)
 
stringAppend(sourceMutablePointer,appendStr)
 
println(String.fromCString(sourceMutablePointer)!) //結(jié)果:Hello,World!

當(dāng)然熙侍,上面這種方式適合所有在Swift中引入C語言的情況榄鉴,但是為了方便調(diào)用,在Swift中默認(rèn)已經(jīng)module了常用的C語言類庫Darwin蛉抓,這個(gè)類庫就作為了標(biāo)準(zhǔn)的Swift類庫不需要再進(jìn)行橋接庆尘,可以直接導(dǎo)入模塊(例如import Darwin,但是事實(shí)上Foundation模塊已經(jīng)默認(rèn)導(dǎo)入了Darwin巷送,而UIKit又導(dǎo)入了Foundation模塊驶忌,因此通常不需要手動(dòng)導(dǎo)入Darwin)。那么對(duì)于沒有模塊化的C語言類庫(包括第三方類庫和自己定義的C語言文件等)能不能不使用橋接文件呢笑跛?答案就是使用隱藏符號(hào)“@asmname”付魔,通過@asmname可以將C語言的函數(shù)不經(jīng)過橋接文件直接映射為Swift函數(shù)。例如可以移除上面的橋接頭文件飞蹂,修改main.swift函數(shù)几苍,通過@asmname加stringAppend映射成為Swift函數(shù)(注意重新映射的Swift函數(shù)名稱不一定和C語言函數(shù)相同):

import Foundation
 
//通過asmname將C函數(shù)stringAppend()映射到Swift函數(shù),事實(shí)上這里的Swift函數(shù)名可以任意命名
@asmname("stringAppend") func stringAppend(var sourceStr:UnsafeMutablePointer,var apendStr:UnsafePointer ) -> Void
 
var sourceStr:String = "Hello"
var appendStr:String = ",World!"
 
var sourceCStr = (sourceStr as NSString).UTF8String
var sourceMutablePointer:UnsafeMutablePointer = UnsafeMutablePointer(sourceCStr)
 
stringAppend(sourceMutablePointer,appendStr)
 
println(String.fromCString(sourceMutablePointer)!) //結(jié)果:Hello,World!

反射

熟悉C#、Java的朋友不難理解反射的概念陈哑,所謂反射就是可以動(dòng)態(tài)獲取類型妻坝、成員信息伸眶,在運(yùn)行時(shí)可以調(diào)用方法、屬性等行為的特性刽宪。 在使用ObjC開發(fā)時(shí)很少強(qiáng)調(diào)其反射概念厘贼,因?yàn)镺bjC的Runtime要比其他語言中的反射強(qiáng)大的多。在ObjC中可以很簡單的實(shí)現(xiàn)字符串和類型的轉(zhuǎn)換(NSClassFromString())圣拄,實(shí)現(xiàn)動(dòng)態(tài)方法調(diào)用(performSelector: withObject:),動(dòng)態(tài)賦值(KVC)等等涂臣,這些功能大家已經(jīng)習(xí)以為常,但是在其他語言中要實(shí)現(xiàn)這些功能卻要跨過較高的門檻售担,而且有些根本就是無法實(shí)現(xiàn)的赁遗。不過在Swift中并不提倡使用Runtime,而是像其他語言一樣使用反射(Reflect)族铆,即使目前Swift中的反射還沒有其他語言中的反射功能強(qiáng)大(Swift還在發(fā)展當(dāng)中岩四,相信后續(xù)版本會(huì)加入更加強(qiáng)大的反射功能)。

在Swift中反射信息通過MirrorType協(xié)議來描述哥攘,而Swift中所有的類型都能通過reflect函數(shù)取得MirrorType信息剖煌。先看一下MirrorType協(xié)議的定義(為了方便大家理解,添加了相關(guān)注釋說明):

protocol MirrorType {
     
    /// 被反射的成員逝淹,類似于一個(gè)實(shí)例做了as Any操作
    var value: Any { get }
     
    /// 被反射成員的類型
    var valueType: Any.Type { get }
     
    /// 被反射成員的唯一標(biāo)識(shí)
    var objectIdentifier: ObjectIdentifier? { get }
     
    /// 被反射成員的子成員數(shù)(例如結(jié)構(gòu)體的成員個(gè)數(shù)耕姊,數(shù)組的元素個(gè)數(shù)等)
    var count: Int { get }
     
    //  取得被反射成員的字成員,返回值對(duì)應(yīng)字成員的名稱和值信息
    subscript (i: Int) -> (String, MirrorType) { get }
     
    /// 對(duì)于反射成員的描述
    var summary: String { get }
     
    /// 顯示在Playground中的“值”信息
    var quickLookObject: QuickLookObject? { get }
     
    /// 被反射成員的類型的種類(例如:基本類型栅葡、結(jié)構(gòu)體茉兰、枚舉、類等)
    var disposition: MirrorDisposition { get }
}

獲取到一個(gè)變量(或常量)的MirrorType之后就可以訪問其類型欣簇、值规脸、類型種類等元數(shù)據(jù)信息。在下面的示例中將編寫一個(gè)函數(shù)簡單實(shí)現(xiàn)一個(gè)類似于ObjC中“valueForKey:”的函數(shù)熊咽。

import UIKit
 
struct Person {
    var name:String
    var age:Int = 0
     
    func showMessage(){
        print("name=\(name),age=\(age)")
    }
}
 
 
//定義一個(gè)方法獲取實(shí)例信息
func valueForKey(key:String,obj:Any) -> Any?{
    //獲取元數(shù)據(jù)信息
    var objInfo:MirrorType = reflect(obj)
    //遍歷子成員
    for index in 0..<objInfo.count {
        //如果子成員名稱等于key則獲取對(duì)應(yīng)值
        let (name,mirror) = objInfo[index]
        if name == key {
            return mirror.value
        }
    }
    return nil;
}
 
var p = Person(name: "Kenshin", age: 29)
//先查看一下對(duì)象描述信息莫鸭,然后對(duì)照結(jié)果是否正確
dump(p)
/*結(jié)果:
__lldb_expr_103.Person
- name: Kenshin
- age: 29
*/
 
var name = valueForKey("name", p)
print("p.name=\(name)") //結(jié)果:p.name=Optional("Kenshin")

可以看到,通過反射可以獲取到變量(或常量)的信息横殴,并且能夠讀取其成員的值被因,但是Swift目前原生并不支持給某個(gè)成員動(dòng)態(tài)設(shè)置值(MirrorType的value屬性是只讀的)。如果想要進(jìn)行動(dòng)態(tài)設(shè)置衫仑,可以利用前面介紹的Swift和ObjC兼容的知識(shí)來實(shí)現(xiàn)梨与,Swift目前已經(jīng)導(dǎo)入了Foundation,只要這個(gè)類是繼承于NSObject就會(huì)有對(duì)應(yīng)的setValue:forKey:方法來使用KVC惑畴。當(dāng)然蛋欣,這僅限于類航徙,對(duì)應(yīng)結(jié)構(gòu)體無能為力如贷。

KVO

和KVC一樣陷虎,在Swift中使用KVO也僅限于NSObject及其子類,因?yàn)镵VO本身就是基于KVC進(jìn)行動(dòng)態(tài)派發(fā)的杠袱,這些都屬于運(yùn)行時(shí)的范疇尚猿。Swift要實(shí)現(xiàn)這些動(dòng)態(tài)特性需要在類型或者成員前面加上@objc(繼承于NSObject的子類及非私有成員會(huì)自動(dòng)添加),但并不是說加了@objc就可以動(dòng)態(tài)派發(fā)楣富,因?yàn)镾wift為了性能考慮會(huì)優(yōu)化為靜態(tài)調(diào)用凿掂。如果確實(shí)需要使用這些特性Swift提供了dynamic關(guān)鍵字來修飾,例如這里要想使用KVO除了繼承于NSObject之外就必須給監(jiān)控的屬性加上dynamic關(guān)鍵字修飾纹蝴。下面的演示中說明了這一點(diǎn):

import Foundation
 
class Acount:NSObject {
    dynamic var balance:Double = 0.0
}
 
class Person:NSObject {
    var name:String
    var account:Acount?{
        didSet{
            if account != nil {
                account!.addObserver(self, forKeyPath: "balance", options: .Old, context: nil);
            }
        }
    }
     
    init(name:String){
        self.name = name
        super.init()
    }
     
    override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer) {
        if keyPath == "balance" {
            var oldValue = change[NSKeyValueChangeOldKey] as! Double
            var newValue = (account?.balance)!
            print("oldValue=\(oldValue),newValue=\(newValue)")
        }
    }
}
 
var p = Person(name: "Kenshin Cui")
var account = Acount()
account.balance = 10000000.0
p.account = account
p.account!.balance = 999999999.9 //結(jié)果:oldValue=10000000.0,newValue=999999999.9

注意:對(duì)于系統(tǒng)類(或一些第三方框架)由于無法修改其源代碼如果要進(jìn)行KVO監(jiān)聽庄萎,可以先繼承此類然后進(jìn)行使用dynamic重寫;此外,并非只有KVO需要加上dynamic關(guān)鍵字塘安,對(duì)于很多動(dòng)態(tài)特性都是如此糠涛,例如要在Swift中實(shí)現(xiàn)Swizzle方法替換,方法前仍然要加上dynamic兼犯,因?yàn)榉椒ǖ奶鎿Q也需要?jiǎng)討B(tài)派發(fā)忍捡。

內(nèi)存管理

循環(huán)引用

Swift使用ARC來自動(dòng)管理內(nèi)存,大多數(shù)情況下開發(fā)人員不需要手動(dòng)管理內(nèi)存切黔,但在使用ObjC開發(fā)時(shí)砸脊,大家都會(huì)遇到循環(huán)引用的問題,在Swift中也不可避免纬霞。 舉例來說凌埂,人員有一個(gè)身份證(Person有idCard屬性),而身份證就有一個(gè)擁有者(IDCard有owner屬性)诗芜,那么對(duì)于一個(gè)Person對(duì)象一旦建立了這種關(guān)系之后就會(huì)和IDCard對(duì)象相互引用而無法被正確的釋放侨舆。

例如下面的代碼在執(zhí)行完test之后p和idCard兩個(gè)對(duì)象均不會(huì)被釋放:

import Foundation
 
class Person {
    var name:String
    var idCard:IDCard
     
    init(name:String,idCard:IDCard){
        self.name = name
        self.idCard = idCard
        idCard.owner = self
    }
     
    deinit{
        println("Person deinit...")
    }
}
 
class IDCard {
    var no:String
    var owner:Person?
     
    init(no:String){
        self.no = no
    }
     
    deinit{
        println("IDCard deinit...")
    }
}
 
func test(){
    var idCard = IDCard(no:"100188888888888888")
    var p = Person(name: "Kenshin Cui",idCard:idCard)
}
 
//注意test執(zhí)行完之后p和idCard均不會(huì)被釋放(無法執(zhí)行deinit方法)
test()
 
println("wait...")

為了避免這個(gè)問題Swift采用了和ObjC中同樣的概念:弱引用,通常將被動(dòng)的一方的引用設(shè)置為弱引用來解決循環(huán)引用問題绢陌。例如這里可以將IDCard中的owner設(shè)置為弱引用挨下。因?yàn)镮DCard對(duì)于Person的引用變成了弱引用,而Person持有IDCard的強(qiáng)引用脐湾,這樣一來Person作為主動(dòng)方臭笆,只要它被釋放后IDCard也會(huì)跟著釋放。如要聲明弱引用可以使用weak和unowned關(guān)鍵字秤掌,前者用于可選類型后者用于非可選類型愁铺,相當(dāng)于ObjC中的__weak和__unsafe_unretained(因?yàn)閣eak聲明的對(duì)象釋放后會(huì)設(shè)置為nil,因此它用來修飾可選類型)闻鉴。

import Foundation
 
class Person {
    var name:String
    var idCard:IDCard
     
    init(name:String,idCard:IDCard){
        self.name = name
        self.idCard = idCard
        idCard.owner = self
    }
     
    deinit{
        println("Person deinit...")
    }
}
 
class IDCard {
    var no:String
    //聲明為弱引用
    weak var owner:Person?
     
    init(no:String){
        self.no = no
    }
     
    deinit{
        println("IDCard deinit...")
    }
}
 
func test(){
    var idCard = IDCard(no:"100188888888888888")
    var p = Person(name: "Kenshin Cui",idCard:idCard)
}
 
//注意test執(zhí)行完之后p會(huì)被釋放茵乱,其后idCard跟著被釋放
test()
 
println("wait...")

當(dāng)然類似于上面的引用關(guān)系實(shí)際遇到的并不多,更多的還是存在于閉包之中(ObjC中多出現(xiàn)于Block中)孟岛,因?yàn)殚]包會(huì)持有其內(nèi)部引用的元素瓶竭。下面簡單修改一下上面的例子督勺,給Person添加一個(gè)閉包屬性,并且在其中訪問self斤贰,這樣閉包自身就和Person類之間形成循環(huán)引用智哀。

import Foundation
 
class Person {
    let name:String
     
    //使用閉包捕獲列表解決循環(huán)引用
    lazy var description:()->NSString = {
        [unowned self] in
        return "name = \(self.name)"
    }
     
    init(name:String){
        self.name = name
    }
     
    deinit{
        println("Person deinit...")
    }
}
 
func test(){
    var p = Person(name: "Kenshin Cui")
    println(p.description())
}
 
test()
 
println("wait...")
/**打印結(jié)果
name = Kenshin Cui
Person deinit...
wait...
 */

指針與內(nèi)存

除了循環(huán)引用問題,Swift之所以將指針類型標(biāo)識(shí)為“unsafe”是因?yàn)橹羔槢]辦法像其他類型一樣進(jìn)行自動(dòng)內(nèi)存管理荧恍,因此有必要了解一下指針和內(nèi)存的關(guān)系瓷叫。在Swift中初始化一個(gè)指針必須通過alloc和initialize兩步,而回收一個(gè)指針需要調(diào)用destroy和dealloc(通常dealloc之后還會(huì)將指針設(shè)置為nil)送巡。

import Foundation
 
class Person {
    var name:String
 
    init(name:String){
        self.name = name
    }
 
    deinit{
        println("Person\(name) deinit...")
    }
}
 
func test(){
    var p = Person(name: "Kenshin Cui")
     
    //雖然可以使用&p作為參數(shù)進(jìn)行inout參數(shù)傳遞摹菠,但是無法直接獲取其地址,下面的做法是錯(cuò)誤的
    //var address = &p
     
    /*創(chuàng)建一個(gè)指向Person的指針pointer*/
    //申請(qǐng)內(nèi)存(alloc參數(shù)代表申請(qǐng)n個(gè)Person類型的內(nèi)存)
    var pointer:UnsafeMutablePointer = UnsafeMutablePointer.alloc(1)
    //初始化
    pointer.initialize(p)
     
    //獲取指針指向的對(duì)象
    var p2 = pointer.memory
    println(p===p2) //結(jié)果:true骗爆,因?yàn)閜和p2指向同一個(gè)對(duì)象
    //修改對(duì)象的值
    p2.name = "Kaoru"
    println(p.name) //結(jié)果:Kaoru
     
     
    //銷毀指針
    pointer.destroy()
    //釋放內(nèi)存
    pointer.dealloc(1)
    //指向空地址
    pointer = nil
}
 
test()
 
println("waiting...")
/**打印結(jié)果
 
Kaoru
PersonKaoru deinit...
waiting...
 
*/

運(yùn)行程序可以看到p對(duì)象在函數(shù)執(zhí)行結(jié)束之后被銷毀辨嗽,但是如果僅僅將pointer設(shè)置為nil是無法銷毀Person對(duì)象的,這很類似于之前的MRC內(nèi)存管理淮腾,在Swift中使用指針需要注意:誰創(chuàng)建(alloc,malloc,calloc)誰釋放糟需。 當(dāng)然上面演示中顯然對(duì)于指針的操作略顯麻煩,如果需要對(duì)一個(gè)變量進(jìn)行指針操作可以借助于Swift中提供的一個(gè)方法withUnsafePointer谷朝。例如想要利用指針修改Person的name就可以采用下面的方式:

var p = Person(name: "Kenshin Cui")
 
var p2 = withUnsafeMutablePointer(&p, {
    (pointer:UnsafeMutablePointer) -> Person in
    pointer.memory.name = "Kaoru"
    return pointer.memory
})
 
println(p.name) //結(jié)果:Kaoru

在前面的C語言系列文章中有一部分內(nèi)容用于介紹如何利用指針遍歷一個(gè)數(shù)組洲押,當(dāng)然在Swift中仍然可以采用這種方式,但是在Swift中如果想要使用指針操作數(shù)組中每個(gè)元素的話通常借助于另一個(gè)類型UnsafeMutableBufferPointer圆凰。這個(gè)類表示一段連續(xù)內(nèi)存杈帐,通常用于表示數(shù)組或字典的指針類型。

import Foundation
 
var array:[String] = ["Kenshin","Kaorsu","Tom"]
 
//UnsafeBufferPointer和UnsafeMutableBufferPointer用于表示一段連續(xù)內(nèi)存的指針专钉,例如:數(shù)組或字典
//下面創(chuàng)建一個(gè)指向數(shù)組的指針
var pointer = UnsafeMutableBufferPointer(start: &array, count: 3)
 
//baseAddress屬性表示內(nèi)存首地址
var baseAddress = pointer.baseAddress as UnsafeMutablePointer
println(baseAddress.memory) //結(jié)果:Kenshin
 
//利用指針遍歷數(shù)組
for index in 1...pointer.count {
    println(baseAddress.memory)
    //向后移動(dòng)指針,向前移動(dòng)使用baseAddress.predecessor()
    baseAddress = baseAddress.successor()
}
/**打印結(jié)果
Kenshin
Kaorsu
Tom
*/

Core Foundation

Core Foundation作為iOS開發(fā)中最重要的框架之一挑童,在iOS開發(fā)中有著重要的地位,但是它是一組C語言接口跃须,在使用時(shí)需要開發(fā)人員自己管理內(nèi)存站叼。在Swift中使用Core Foundation框架(包括其他Core開頭的框架)需要區(qū)分這個(gè)API返回的對(duì)象是否進(jìn)行了標(biāo)注:

1.如果已經(jīng)標(biāo)注則在使用時(shí)完全不用考慮內(nèi)存管理(它可以自動(dòng)管理內(nèi)存)。

2.如果沒有標(biāo)注則編譯器不會(huì)進(jìn)行內(nèi)存管理托管菇民,此時(shí)需要將這個(gè)非托管對(duì)象轉(zhuǎn)化為托管對(duì)象(當(dāng)然你也可以使用retain()尽楔、release()或者autorelease()手動(dòng)管理內(nèi)存,但是不推薦這么做)第练。當(dāng)然阔馋,蘋果開發(fā)工具組會(huì)盡可能的標(biāo)注這些API以實(shí)現(xiàn)C代碼和Swift的自動(dòng)橋接,但是在此之前未標(biāo)注的API會(huì)返回Unmanaged<Type>結(jié)構(gòu)娇掏,可以調(diào)用takeUnretainedValue()和takeRetainedValue()方法將其轉(zhuǎn)化為可以自動(dòng)進(jìn)行內(nèi)存管理的托管對(duì)象(具體是調(diào)用前者還是后者呕寝,需要根據(jù)是否需要開發(fā)者自己進(jìn)行內(nèi)存管理而定,其本質(zhì)是使用takeRetainedValue()方法婴梧,在對(duì)象使用完之后會(huì)調(diào)用一次release()方法下梢。按照Core Foundation的命名標(biāo)準(zhǔn)客蹋,通常如果函數(shù)名中含“Create”晶衷、“Copy”奋岁、“Retain”關(guān)鍵字需要調(diào)用takeRetainedValue()方法來轉(zhuǎn)化成托管對(duì)象)他去。

當(dāng)然,上述兩種方式均是針對(duì)系統(tǒng)框架而言竟坛,如果是開發(fā)者編寫的類或者第三方類庫,應(yīng)該盡可能按照Cocoa規(guī)范命名并且在合適的地方使用CF_RETURNS_RETAINED和CF_RETURNS_NOT_RETAINED來進(jìn)行標(biāo)注以便可以進(jìn)行自動(dòng)內(nèi)存管理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末钧舌,一起剝皮案震驚了整個(gè)濱河市担汤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌洼冻,老刑警劉巖崭歧,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異撞牢,居然都是意外死亡率碾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門屋彪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來所宰,“玉大人,你說我怎么就攤上這事畜挥∽兄啵” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵蟹但,是天一觀的道長躯泰。 經(jīng)常有香客問我,道長华糖,這世上最難降的妖魔是什么麦向? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮客叉,結(jié)果婚禮上磕蛇,老公的妹妹穿的比我還像新娘。我一直安慰自己十办,他們只是感情好秀撇,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著向族,像睡著了一般呵燕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上件相,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天再扭,我揣著相機(jī)與錄音氧苍,去河邊找鬼。 笑死泛范,一個(gè)胖子當(dāng)著我的面吹牛让虐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播罢荡,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼赡突,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了区赵?” 一聲冷哼從身側(cè)響起惭缰,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎笼才,沒想到半個(gè)月后漱受,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡骡送,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年昂羡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摔踱。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡紧憾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昌渤,到底是詐尸還是另有隱情赴穗,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布膀息,位于F島的核電站般眉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏潜支。R本人自食惡果不足惜甸赃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望冗酿。 院中可真熱鬧埠对,春花似錦、人聲如沸裁替。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弱判。三九已至襟沮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背开伏。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來泰國打工膀跌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人固灵。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓捅伤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親巫玻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子丛忆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

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