iOS開發系列--Swift進階

概述

上一篇文章《iOS開發系列--Swift語言》中對Swift的語法特色以及它和C、ObjC等其餘語言的用法區別進行了介紹。固然,這只是Swift的入門基礎,可是僅僅瞭解這些對於使用Swift進行iOS開發仍是不夠的。在這篇文章中將繼續介紹一些Swift開發中一些不常關注可是又必備的知識點,以便對Swift有進一步的瞭解。html

  1. 訪問控制
  2. Swift命名空間
  3. Swift和ObjC互相調用
    1. Swift和ObjC映射關係
    2. Swift調用ObjC
    3. ObjC調用Swift
    4. 擴展—Swift調用C
  4. 反射
    1. 擴展—KVO
  5. 內存管理
    1. 循環引用
    2. 指針與內存
    3. 擴展—Core Foundation

訪問控制

和其餘高級語言同樣Swift中也增長了訪問控制,在Swift中提供了private、internal、public三種訪問級別,可是不一樣的是Swift中的訪問級別是基於模塊(module,或者target)和源文件(.swift文件)的,而不是基於類型、命名空間聲明。git

  • private:只能訪問當前源文件中的實體(注意Swift中的private和其餘語言不太同樣,它是基於源文件的,做用範圍是整個源文件,若是一個源文件中有兩個類,那麼一個類能夠訪問另一個類的私有成員)。
  • internal:能夠訪問當前模塊中的其餘任何實體,可是在模塊外沒法訪問,這是全部實體的默認訪問級別(一般在一個單目標Application中不須要自行設置訪問級別)。
  • public:能夠訪問當前模塊及其餘模塊中的任何實體(一般用於Framework)。

下面是關於Swift關於不一樣成員訪問級別的約定規則:github

  1. 若是一個類的訪問級別是private那麼該類的全部成員都是private(此時成員沒法修改訪問級別),若是一個類的訪問級別是internal或者public那麼它的全部成員都是internal(若是類的訪問級別是public,成員默認internal,此時能夠單獨修改爲員的訪問級別),類成員的訪問級別不能高於類的訪問級別(注意:嵌套類型的訪問級別也符合此條規則);
  2. 常量、變量、屬性、下標腳本訪問級別低於其所聲明的類型級別,而且若是不是默認訪問級別(internal)要明確聲明訪問級別(例如一個常量是一個private類型的類類型,那麼此常量必須聲明爲private);
  3. 在不違反一、2兩條規則的狀況下,setter的訪問級別能夠低於getter的訪問級別(例如一個屬性訪問級別是internal,那麼能夠添加private(set)修飾將setter權限設置爲private,在當前模塊中只有此源文件能夠訪問,對外部是隻讀的);
  4. 必要構造方法(required修飾)的訪問級別必須和類訪問級別相同,結構體的默認逐一構造函數的訪問級別不高於其成員的訪問級別(例如一個成員是private那麼這個構造函數就是private,可是能夠經過自定義來聲明一個public的構造函數),其餘方法(包括其餘構造方法和普通方法)的訪問級別遵循規則1;
  5. 子類的訪問級別不高於父類的訪問級別,可是在遵循三種訪問級別做用範圍的前提下子類能夠將父類低訪問級別的成員重寫成更高的訪問級別(例如父類A和子類B在同一個源文件,A的訪問級別是public,B的訪問級別是internal,其中A有一個private方法,那麼A能夠覆蓋其private方法並重寫爲internal);
  6. 協議中全部必須實現的成員的訪問級別和協議自己的訪問級別相同,其子協議的訪問級別不高於父協議;
  7. 若是一個類繼承於另外一個類的同時實現了某個協議那麼這個類的訪問級別爲父類和協議的最低訪問級別,而且此類中方法訪問級別和所實現的協議中的方法相同;
  8. 擴展的成員訪問級別遵循規則1,可是對於類、結構體、枚舉的擴展能夠明確聲明訪問級別而且能夠更低(例如對於internal的類,你能夠聲明一個private的擴展),而協議的訪問級別不能夠明確聲明;
  9. 元組的訪問級別是元組中各個元素的最低訪問級別,注意:元組的訪問級別是自動推導的,沒法直接使用以上三個關鍵字修飾其訪問級別;
  10. 函數的訪問級是函數的參數、返回值的最低級別,而且若是其訪問級別和默認訪問級別(internal)不符須要明確聲明;
  11. 枚舉成員的訪問級別等同於枚舉的訪問級別(沒法單獨設置),同時枚舉的原始值、關聯值的訪問級別不能低於枚舉的訪問級別;
  12. 泛型類型或泛型函數的訪問級別是泛型類型、泛型函數、泛型類型參數三者中最低的一個;
  13. 類型別名的訪問級別不能高於原類型的訪問級別;

 上面這些規則看上去比較繁瑣,但其實不少內容理解起來也是瓜熟蒂落的(若是你是一個語言設計者相信大部分規則也會這麼設計),下面經過一個例子對於規則3作一解釋,這一點和其餘語言有所不一樣可是卻更加實用。在使用ObjC開發時你們一般會有這樣的經驗:在一個類中但願某個屬性對外界是隻讀的,可是本身又須要在類中對屬性進行寫操做,此時只能直接訪問屬性對應的成員變量,而不能直接訪問屬性進行設置。可是Swift爲了讓語法儘量精簡,並無成員變量的概念,此時就能夠經過訪問控制來實現。編程

Person.swiftswift

import Foundation

public class Person {
    //設置setter私有,可是getter爲public
    public private(set) var name:String
    
    public init(name:String){
        self.name = name
    }
    
    public func showMessage(){
        println("name=\(name)")
    }
}

main.swift數組

import Foundation

var p =  Person(name:"Kenshin")
//此時不能設置name屬性,可是可讀
//p.name = "Kaoru"
println("name=\(p.name)")
p.showMessage() 

Xcode中的每一個構建目標(Target)能夠當作是一個模塊(Module),這個構建目標能夠是一個Application,也能夠是一個通用的Framework(更多的時候是一個Application)。安全

Swift命名空間

熟悉ObjC的朋友都知道ObjC沒有命名空間,爲了不類名重複蘋果官方推薦使用類名前綴,這種作法從必定程度上避免了大部分問題,可是當你在項目中引入一個第三方庫而這個第三方庫引用了一個和你當前項目中用到的同一個庫時就會出現問題。由於靜態庫最終會編譯到同一個域,最終致使編譯出錯。固然做爲一個現代化語言Swift必定會解決這個問題,但是若是查看Swift的官方文檔,裏面關於Swift的命名空間並無太多詳細的說明。可是Swift的做者Chris Lattner在Twitter中回答了這個問題:網絡

Namespacing is implicit in swift, all classes (etc) are implicitly scoped by the module (Xcode target) they are in. no class prefixes needed閉包

Swift中是實現了命名空間功能的,只是這個命名空間不像C#的namespace或者Java中的package那樣須要顯式在文件中指定,而是採用模塊(Module)的概念:在同一個模塊中全部的Swift類處於同一個命名空間,它們之間不須要導入就能夠相互訪問。很明顯Swift的這種作法是爲了最大限度的簡化Swift編程。其實一個module就能夠當作是一個project中的一個target,在建立項目的時候默認就會建立一個target,這個target的默認模塊名稱就是這個項目的名稱(能夠在target的Build Settings—Product Module Name配置)。app

下面不妨看一個命名空間的例子,建立一個Single View Application應用「NameSpaceDemo」。默認狀況下模塊名稱爲「NameSpaceDemo」,這裏修改成「Network」,而且添加」HttpRequest.swift"。而後添加一個Cocoa Touch Framework類型的target並命名爲「IO」,添加「File.swift」。而後在ViewController.swift中調用HttpRequest發送請求,將請求結果利用File類來保存起來。

File.swift

import Foundation

public class File {
    public var path:String!
    
    public init(path:String) {
        self.path = path
    }
    
    public func write(content:String){
        var error:NSError?
        content.writeToFile(path, atomically: true, encoding:NSUTF8StringEncoding, error: &error)
        if error != nil {
            println("write failure...")
        }
    }
    
    public func read() ->String?{
        var error:NSError?
        var content = String(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: &error)
        if error != nil {
            println("write failure...")
        }
        return content
    }
}

HttpRequest.swift

import Foundation

class HttpRequest {
    class func request(urlStr:String,complete:(responseText:String?)->()){
        var url = NSURL(string: urlStr)
        let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) -> Void in
            var str:String?
            if error == nil {
                str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String
            }
            complete(responseText: str)
        }
        task.resume()
    }
}

 

ViewController.swift

import UIKit

//導入模塊
import IO

class ViewController: UIViewController {
    let url = "http://www.cnblogs.com/kenshincui"
    let filePath = "/Users/KenshinCui/Desktop/file.txt"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //加上命名空間Network調用,注意這裏命名空間能夠省略
        Network.HttpRequest.request(url, complete: { (responseText) -> () in
            if let txt = responseText {
                
                //調用模塊中的類和方法
                var file = File(path: self.filePath)
                file.write(txt)
//                println(file.read()!)
                
            }else{
                println("error...")
            }
        })
        
    }
}

能夠看到首先同一個Module中的HttpRequest類能夠加上命名空間調用(固然這裏能夠省略),另外對於不一樣Modle下的File類經過導入IO模塊能夠直接使用File類,可是這裏須要注意訪問控制,能夠看到File類及其成員均聲明爲了public訪問級別。 用模塊進行命名空間劃分的方式好處就是能夠不用顯式指定命名空間,然而這種方式沒法在同一個模塊中再進行劃分,不過這個問題可使用Swift中的嵌套類型來解決。在下面的例子中仍然使用前面的兩個類HttpRequest和File類來演示,不一樣的是兩個類分別嵌套在兩個結構體Network和IO之中。

Network.HttpRequest.swift

import Foundation

struct Network {
    class HttpRequest {
        class func request(urlStr:String,complete:(responseText:String?)->()){
            var url = NSURL(string: urlStr)
            let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) -> Void in
                var str:String?
                if error == nil {
                    str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String
                }
                complete(responseText: str)
            }
            task.resume()
        }
    }
}

IO.File.swift

import Foundation

struct IO {
    class File {
        var path:String!
        
        init(path:String) {
            self.path = path
        }
        
        func write(content:String){
            var error:NSError?
            content.writeToFile(path, atomically: true, encoding:NSUTF8StringEncoding, error: &error)
            if error != nil {
                println("write failure...")
            }
        }
        
        func read() ->String?{
            var error:NSError?
            var content = String(contentsOfFile: path, encoding: NSUTF8StringEncoding, error: &error)
            if error != nil {
                println("write failure...")
            }
            return content
        }
    }
}

main.swift

import Foundation

let url = "http://www.cnblogs.com/kenshincui"
let filePath = "/Users/KenshinCui/Desktop/file.txt"

Network.HttpRequest.request(url, complete: { (responseText) -> () in
    if let txt = responseText {
        
        var file = IO.File(path: filePath)
        file.write(txt)
        //println(file.read()!)
        
    }else{
        println("error...")
    }
})

sleep(30) //延遲30s避免命令行程序運行完進程結束,等待網絡請求

Swift和ObjC互相調用

Swift的設計的初衷就是擺脫ObjC沉重的歷史包袱,畢竟ObjC的歷史太過悠久,相比於不少現代化語言它缺乏一些很酷的語法特性,並且ObjC的語法和其餘語言相比差異很大。可是Apple同時也不能忽視ObjC的地位,畢竟ObjC通過二十多年的歷史積累了大量的資源(開發者、框架、類庫等),所以在Swift推出的初期必須考慮兼容ObjC。但同時Swift和ObjC是基於兩種不一樣的方式來實現的(例如ObjC能夠在運行時決定對象類型,可是Swift爲了提升效率要求在編譯時就必須肯定對象類型),因此要無縫兼容須要作大量的工做。而做爲開發人員咱們有必要了解兩種語言之間的轉化關係才能對Swift有更深入的理解。

Swift和ObjC映射關係

其實從前面的例子中你們不難發現Swift和ObjC必然存在着必定的映射關係,例如對於文件的操做使用了字符串的writeToFile方法,在網絡請求時使用的NSURLSession,雖然調用方式不一樣可是其參數徹底和作ObjC開發時調用方式一致。緣由就是Swift編譯器自動作了映射,下面列舉了部分Swift和ObjC的映射關係幫助你們理解:

Swift ObjC 備註
AnyObject id(ObjC中的對象任意類型) 因爲ObjC中的對象可能爲nil,因此Swift中若是用到ObjC中類型的參數會標記爲對應的可選類型
Array、Dictionary、Set NSArray、NSDictionary、NSSet 注意:ObjC中的數組和字典不能存儲基本數據類型,只能存儲對象類型,這樣一來對於Swift中的Int、UInt、Float、Double、Bool轉化時會自動橋接成NSNumber
Int NSInteger、NSUInteger 其餘基本類型狀況相似,再也不一一列舉
NSObjectProtocol NSObject協議(注意不是NSObject類) 因爲Swift在繼承或者實現時沒有類的命名空間的概念,而ObjC中既有NSObject類又有NSObject協議,因此在Swift中將NSObject協議對應成了NSObjectProtocol
CGContext CGContextRef

Core Foundation中其餘狀況均是如此,因爲Swift自己就是引用類型,在Swift不須要再加上「Ref」

ErrorType NSError  
「ab:" @selector(ab:)

Swift能夠自動將字符串轉化成成selector

@NSCopying copy屬性  
init(x:X,y:Y) initWithX:(X)x y:(Y)y 構造方法映射,Swift會去掉「With」而且第一個字母小寫做爲其第一個參數,同時也不須要調用alloc方法,可是須要注意ObjC中的便利工廠方法(構建對象的靜態方法)對應成了Swift的便利構造方法
func xY(a:A,b:B) void xY:(A)a b:(B)b  
extension(擴展) category(分類) 注意:不能爲ObjC中存在的方法進行extension
Closure(閉包) block(塊) 注意:Swift中的閉包能夠直接修改外部變量,可是block中要修改外部變量必須聲明爲__block

Swift兼容大部分ObjC(經過相似上面的對應關係),多數ObjC的功能在Swift中都能使用。固然,仍是有個別地方Swift並無考慮兼容ObjC,例如:Swift中沒法使用預處理指令(例如:宏定義,事實上在Swift中推舉使用常量定義);Swift中也沒法使用performSelector來執行一個方法,由於Swift認爲這麼作是不安全的。

相反,若是在ObjC中使用Swift也一樣是可行的(除了個別Swift新增的高級功能)。Swift中若是一個類繼承於NSObject,那麼他會自動和ObjC兼容,這樣ObjC就能夠按照上面的對應關係調用Swift的方法、屬性等。可是若是Swift中的類沒有繼承於NSObject呢?此時就須要使用一個關鍵字「@objc」進行標註,ObjC就能夠像使用正常的ObjC編碼同樣調用Swift了(事實上繼承於NSObject的類之因此在ObjC中可以直接調用也是由於編譯器會自動給類和非private成員添加上@objc,相似的@IBoutlet、@IBAction、@NSManaged修飾的方法屬性Swift編譯器也會自動添加@objc標記)。

Swift調用ObjC 

當前ObjC已經積累了大量的第三方庫,相信在Swift發展的前期調用已經存在的ObjC是比較常見的。在Swift和ObjC的兼容性容許你在一個項目中使用兩種語言混合編程(稱爲「mix and match」),而無論這個項目本來是基於Swift的仍是ObjC的。不管是Swift中調用ObjC仍是ObjC中調用Swift都是經過頭文件暴漏對應接口的,下圖說明了這種交互方式:

SwiftInteractObjC

不難發現,要在Swift中調用ObjC必須藉助於一個橋接頭文件,在這個頭文件中將ObjC接口暴漏給Swift。例如你能夠建立一個「xx.h」頭文件,而後使用「#import」導入須要在Swift中使用的ObjC類,同時在Build Settings的「Objective-C Bridging Header」中配置橋接文件「xx.h」。可是好在這個過程Xcode能夠幫助你完成,你只須要在Swift項目中添加ObjC文件,Xcode就會詢問你是否建立橋接文件,你只須要點擊「Yes」就能夠幫你完成上面的操做:

CreateObjCBridgingHeaderTip

爲了演示Swift中調用ObjC的簡潔性, 下面建立一個基於Swift的Single View Application類型的項目,如今有一個基於ObjC的「KCLoadingView」類,它能夠在網絡忙時顯示一個加載動畫。整個類的實現很簡單,就是經過一個基礎動畫實現一個圖片的旋轉。

KCLoadingView.h

#import <UIKit/UIKit.h>

/**
 *  加載視圖,顯示加載效果
 */
@interface KCLoadingView : UIImageView

/**
 *  啓動,開始旋轉
 */
- (void)start;

/**
 *  中止
 */
- (void)stop;

@end

KCLoadingView.m

#import "KCLoadingView.h"

static NSString *const kAnimationKey = @"rotationAnimation";
@interface KCLoadingView ()
@property(strong, nonatomic) CABasicAnimation *rotationAnimation;
@end

@implementation KCLoadingView
#pragma mark - 生命週期及其基類方法
- (instancetype)initWithFrame:(CGRect)frame {
	if (self = [super initWithFrame:frame]) {
		[self setup];
	}
	return self;
}

#pragma mark - 公共方法
- (void)start {
	[self.layer addAnimation:self.rotationAnimation forKey:kAnimationKey];
}

- (void)stop {
	[self.layer removeAnimationForKey:kAnimationKey];
}

#pragma mark - 私有方法
- (void)setup {
	self.image = [UIImage imageNamed:@"loading"];

	CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
	rotationAnimation.toValue = [NSNumber numberWithFloat:M_PI * 2.0];
	rotationAnimation.duration = 0.7;
	rotationAnimation.cumulative = YES;
	rotationAnimation.repeatCount = HUGE_VALF;
	self.rotationAnimation = rotationAnimation;
	[self.layer addAnimation:rotationAnimation forKey:kAnimationKey];
}

@end

當將這個類加入到項目時就會提示你是否建立一個橋接文件,在這個文件中導入上面的「KCLoadingView」類。如今這個文件只有一行代碼

ObjCBridge-Bridging-Header.h

#import "KCLoadingView.h" 

接下來就能夠調用這個類完成一個加載動畫,調用關係徹底順其天然,開發者根本感受不到這是在調用一個ObjC類。

ViewController.swfit

import UIKit

class ViewController: UIViewController {
    lazy var loadingView:KCLoadingView = {
        var size=UIScreen.mainScreen().bounds.size
        var lv = KCLoadingView()
        lv.frame.size=CGSizeMake(37.0, 37.0)
        lv.center=CGPointMake(size.width*0.5, size.height*0.5)
        return lv
    }()
    
    lazy private var converView:UIView = {
        var cv = UIView(frame: UIScreen.mainScreen().bounds)
        cv.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.5)
        return cv
    }()
    
    override func loadView() {
        //設置背景
        var image = UIImage(named: "iOS9")
        var background = UIImageView(frame: UIScreen.mainScreen().bounds)
        background.userInteractionEnabled=true
        background.image=image
        self.view = background
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //設置蒙層
        self.view.addSubview(self.converView)
        
        //添加加載控件
        self.view.addSubview(self.loadingView)
        
        loadingView.start()
    }
    
    override func touchesBegan(touches: Set, withEvent event: UIEvent) {
        loadingView.stop()
    }
}

運行效果

SwiftUseObjC 

ObjC調用Swift

從前面的Swift和ObjC之間的交互圖示能夠看到ObjC調用Swift是經過Swift生成的一個頭文件實現的,好在這個頭文件是由編譯器自動完成的,開發者不須要關注,只須要記得他的格式便可「項目名稱-Swift.h」。若是在ObjC項目中使用了Swift,只要在ObjC的「.m」文件中導入這個頭文件就能夠直接調用Swift,注意這個生成的文件並不在項目中,它在項目構建的一個文件夾中(能夠按住Command點擊頭文件查看)。一樣經過前面的例子演示如何在ObjC中調用Swift,新建一個基於ObjC的項目(項目名稱「UseSwiftInObjC」),而且此次加載動畫控件使用Swift編寫。

LoadingView.swift

import UIKit

public class LoadingView:UIImageView {
    let basicAnimationKey = "rotationAnimation"
    lazy var rotationAnimation:CABasicAnimation = {
        var animation = CABasicAnimation(keyPath: "transform.rotation.z")
        animation.toValue = 2*M_PI
        animation.duration = 0.7
        animation.cumulative = true
        animation.repeatCount = .infinity
        return animation
    }()
    
    convenience init(){
        self.init(frame: CGRectZero)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.image = UIImage(named: "loading")
    }

    required public init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.image = UIImage(named: "loading")
    }
    
    public func start() {
        self.layer.addAnimation(self.rotationAnimation, forKey: basicAnimationKey)
    }
    
    public func stop() {
        self.layer.removeAnimationForKey(basicAnimationKey)
    }
}

 而後能夠直接在ObjC代碼中導入自動生成的文件「UseSwiftInObjC-Swift.h」並調用。

ViewController.m

#import "ViewController.h"
#import "UseSwiftInObjC-Swift.h"

@interface ViewController ()
@property (strong,nonatomic) UIView *converView;
@property (strong,nonatomic) LoadingView *loadingView;
@end

@implementation ViewController
-(void)loadView{
    UIImage *image = [UIImage imageNamed:@"iOS9"];
    UIImageView *background = [[UIImageView alloc]initWithImage:image];
    background.userInteractionEnabled = YES;
    self.view = background;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.view addSubview:self.converView];
    [self.view addSubview:self.loadingView];
    [self.loadingView start];
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    [self.loadingView stop];
}

#pragma mark - 屬性
/**
 *  遮罩層
 */
-(UIView *)converView{
    if (!_converView) {
        _converView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
        _converView.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5];
    }
    return _converView;
}
/**
 *  加載指示器
 */
-(LoadingView *)loadingView{
    if(!_loadingView){
        CGSize screenSize = [UIScreen mainScreen].bounds.size;
        CGFloat loadingViewWidth = 37.0;
        _loadingView=[[LoadingView alloc]init];
        _loadingView.frame=CGRectMake((screenSize.width-loadingViewWidth)*0.5, (screenSize.height - loadingViewWidth)*0.5, loadingViewWidth, loadingViewWidth);
    }
    return _loadingView;
}

@end

雖然生成的頭文件並不會直接放到項目中,可是能夠直接按着Command鍵查看生成的文件內容,固然這個文件比較長,裏面使用了不少宏定義判斷,這裏只關心最主要部分。

UseSwiftInObjC-Swift.h

SWIFT_CLASS("_TtC14UseSwiftInObjC11LoadingView")
@interface LoadingView : UIImageView
- (SWIFT_NULLABILITY(nonnull) instancetype)initWithCoder:(NSCoder * __nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER;
- (void)start;
- (void)stop;
@end

能夠清晰的看到Swift確實進行了橋接,經過頭文件將接口暴漏給了ObjC。可是注意前面說過的訪問控制,若是類和方法在Swift中不聲明爲public,那麼在ViewController.m中是沒法調用的。事實上,若是方法不是public在UseSwiftInObjC-Swift.h中根本不會生成對應的方法聲明。

擴展—Swift調用C

因爲ObjC是C的超集,使得在ObjC能夠無縫訪問C語言。可是Swift的產生就是ObjC without C,所以在Swift中不可能像在ObjC中混編入C同樣簡單。可是考慮到C語言的強大以及歷時那麼多年留下了豐富的類庫,有時候又不得不使用它,Swift中仍是保留了與必定數量的C語言類型和特性的兼容。前面介紹過關於如何在Swift中使用ObjC的知識,事實上在Swift中使用C也是相似的(由於ObjC是C的超集,ObjC既然能夠橋接,C天然也能夠),你須要一個橋接文件,不一樣的是ObjC中的不少內容在橋接到Swift時都是相似,很容易上手。例如ObjC中使用的NSObject,在Swift中仍然對應NSObject,不少時候開發人員感受不到這種轉化,只是編程語言發生了變化。可是C導入Swift就須要必需要了解具體的對應關係:

C類型 Swift類型 說明
基本類型    
char,signed char CChar 相似的unsigned char對應CUnsignedChar
int CInt 相似的unsigned int對應CUnsignedInt
short CShort 相似的unsigned short對應CUnsignedShort
long CLong 相似的unsigned long對應CUnsignedLong
long long CLongLong 相似的unsigned long long 對應 CUnsignedLongLong
float CFloat  
double CDouble  
構造體類型   注意:結構體實現
枚舉typedef NS_ENUM(NSInteger,A){AB,AC} enum A:Int{case B,C} 去掉對應的前綴 ,注意C中的NS_Options會對應成Swift中實現OptionSetType的結構體
結構體 對應Swift中的結構體   
聯合   Swift中不能徹底支持聯合,建議使用枚舉關聯值代替
指針類型   C語言中的指針類型映射成了Swift中的泛型
Type * UnsafeMutablePointer<Type> 做爲返回類型、變量、參數類型時
const Type * UnsafePointer<Type> 做爲返回類型、變量、參數類型時
Type *const * UnsafePointer<Type> 對於類類型
Type *__strong * UnsafeMutablePointer<Type> 對於類類型
Type * * AutoreleasingUnsafePointer<Type> 對於類類型
函數指針 閉包  

 對於其餘類型的映射關係都很容易理解,這裏主要說一下指針的內容。經過上表能夠看到在C中定義的一些指針類型當在Swift中使用時會有對應的類型,可是若是一個參數爲某種指針類型,實際調用時應該使用何種Swift數據類型的數據做爲參數調用呢?例如參數爲UnsafePointer<Type>,是否只能傳入UnsafePointer<Type>呢,其實也能夠傳入nil,而且最終調用時將會轉化爲null指針來調用。下表列出了這種參數調用對應關係:

可用類型 最終轉化類型
 UnsafePointer<Type>   注意:若是Type爲Void則能夠表明任何類型
 nil  null
 UnsafePointer<Type>、UnsafeMutablePointer<Type>、AutoreleasingUnsafeMutablePointer<Type>  UnsafePointer<Type>
 String  若是Type爲Int、或者Int8將最終轉化爲UTF8字符串
 &typeValue  元素地址
 Type類型的數組([typeValue1,typeValue2])  數組首地址
 UnsafeMutablePointer<Type>  注意:若是Type爲Void則能夠表明任何類型
 nil null 
 UnsafeMutablePointer<Type>  UnsafeMutablePointer<Type> 
 &typeValue  元素地址
 Type類型的數組的地址(&[typeValue1,typeValue2])  數組地址
 AutoreleasingUnsafeMutablePointer<Type>  
 nil null 
 AutoreleasingUnsafeMutablePointer<Type>  AutoreleasingUnsafeMutablePointer<Type>
 &typeValue  元素地址

下面不妨看一下如何在Swift中使用C語言,假設如今有一個用於字符串拼接的C庫函數「stringAppend(char*,const char *)」,將其對應的文件導入到一個Swift項目中按照提示添加橋接頭文件並在橋接頭文件中引入對應的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-Bridging-Header.h

#import "string.h"

而後在Swift中調用上面的C函數

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)!) //結果:Hello,World!

 能夠看到「char *」參數轉化成了Swift中的UnsafeMutablePointer<Int8>,而將」const char *」轉化成了UnsafePointer<Int8>。根據上面表格中的調用關係,若是參數爲UnsafeMutablePointer<Type>能夠傳入nil、UnsafeMutablePointer<Type>或者元素地址,很明顯這裏須要使用UnsafeMutablePointer<Int8>;而若是參數爲UnsafePointer<Type>而且Type爲Int8或者Int則能夠直接傳入String類型的參數,所以也就有了上面的調用關係。

固然,上面這種方式適合全部在Swift中引入C語言的狀況,可是爲了方便調用,在Swift中默認已經module了經常使用的C語言類庫Darwin,這個類庫就做爲了標準的Swift類庫不須要再進行橋接,能夠直接導入模塊(例如import Darwin,可是事實上Foundation模塊已經默認導入了Darwin,而UIKit又導入了Foundation模塊,所以一般不須要手動導入Darwin)。那麼對於沒有模塊化的C語言類庫(包括第三方類庫和本身定義的C語言文件等)能不能不使用橋接文件呢?答案就是使用隱藏符號「@asmname」,經過@asmname能夠將C語言的函數不通過橋接文件直接映射爲Swift函數。例如能夠移除上面的橋接頭文件,修改main.swift函數,經過@asmname加stringAppend映射成爲Swift函數(注意從新映射的Swift函數名稱不必定和C語言函數相同):

 main.swift

import Foundation

//經過asmname將C函數stringAppend()映射到Swift函數,事實上這裏的Swift函數名能夠任意命名
@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)!) //結果:Hello,World!

更多Swift標準類庫信息能夠查看:https://github.com/andelf/Defines-Swift 

反射

熟悉C#、Java的朋友不難理解反射的概念,所謂反射就是能夠動態獲取類型、成員信息,在運行時能夠調用方法、屬性等行爲的特性。 在使用ObjC開發時不多強調其反射概念,由於ObjC的Runtime要比其餘語言中的反射強大的多。在ObjC中能夠很簡單的實現字符串和類型的轉換(NSClassFromString()),實現動態方法調用(performSelector: withObject:),動態賦值(KVC)等等,這些功能你們已經習覺得常,可是在其餘語言中要實現這些功能卻要跨過較高的門檻,並且有些根本就是沒法實現的。不過在Swift中並不提倡使用Runtime,而是像其餘語言同樣使用反射(Reflect),即便目前Swift中的反射尚未其餘語言中的反射功能強大(Swift還在發展當中,相信後續版本會加入更增強大的反射功能)。

在Swift中反射信息經過MirrorType協議來描述,而Swift中全部的類型都能經過reflect函數取得MirrorType信息。先看一下MirrorType協議的定義(爲了方便你們理解,添加了相關注釋說明):

protocol MirrorType {
    
    /// 被反射的成員,相似於一個實例作了as Any操做
    var value: Any { get }
    
    /// 被反射成員的類型
    var valueType: Any.Type { get }
    
    /// 被反射成員的惟一標識
    var objectIdentifier: ObjectIdentifier? { get }
    
    /// 被反射成員的子成員數(例如結構體的成員個數,數組的元素個數等)
    var count: Int { get }
    
    //  取得被反射成員的字成員,返回值對應字成員的名稱和值信息
    subscript (i: Int) -> (String, MirrorType) { get }
    
    /// 對於反射成員的描述
    var summary: String { get }
    
    /// 顯示在Playground中的「值」信息
    var quickLookObject: QuickLookObject? { get }
    
    /// 被反射成員的類型的種類(例如:基本類型、結構體、枚舉、類等)
    var disposition: MirrorDisposition { get }
}

獲取到一個變量(或常量)的MirrorType以後就能夠訪問其類型、值、類型種類等元數據信息。在下面的示例中將編寫一個函數簡單實現一個相似於ObjC中「valueForKey:」的函數。

import UIKit

struct Person {
    var name:String
    var age:Int = 0
    
    func showMessage(){
        print("name=\(name),age=\(age)")
    }
}


//定義一個方法獲取實例信息
func valueForKey(key:String,obj:Any) -> Any?{
    //獲取元數據信息
    var objInfo:MirrorType = reflect(obj)
    //遍歷子成員
    for index in 0..<objInfo.count {
        //若是子成員名稱等於key則獲取對應值
        let (name,mirror) = objInfo[index]
        if name == key {
            return mirror.value
        }
    }
    return nil;
}

var p = Person(name: "Kenshin", age: 29)
//先查看一下對象描述信息,而後對照結果是否正確
dump(p)
/*結果:
__lldb_expr_103.Person
- name: Kenshin
- age: 29
*/

var name = valueForKey("name", p)
print("p.name=\(name)") //結果:p.name=Optional("Kenshin")

能夠看到,經過反射能夠獲取到變量(或常量)的信息,而且可以讀取其成員的值,可是Swift目前原生並不支持給某個成員動態設置值(MirrorType的value屬性是隻讀的)。若是想要進行動態設置,能夠利用前面介紹的Swift和ObjC兼容的知識來實現,Swift目前已經導入了Foundation,只要這個類是繼承於NSObject就會有對應的setValue:forKey:方法來使用KVC。固然,這僅限於類,對應結構體無能爲力。

擴展--KVO

和KVC同樣,在Swift中使用KVO也僅限於NSObject及其子類,由於KVO自己就是基於KVC進行動態派發的,這些都屬於運行時的範疇。Swift要實現這些動態特性須要在類型或者成員前面加上@objc(繼承於NSObject的子類及非私有成員會自動添加),但並非說加了@objc就能夠動態派發,由於Swift爲了性能考慮會優化爲靜態調用。若是確實須要使用這些特性Swift提供了dynamic關鍵字來修飾,例如這裏要想使用KVO除了繼承於NSObject以外就必須給監控的屬性加上dynamic關鍵字修飾。下面的演示中說明了這一點:

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 //結果:oldValue=10000000.0,newValue=999999999.9

注意:對於系統類(或一些第三方框架)因爲沒法修改其源代碼若是要進行KVO監聽,能夠先繼承此類而後進行使用dynamic重寫;此外,並不是只有KVO須要加上dynamic關鍵字,對於不少動態特性都是如此,例如要在Swift中實現Swizzle方法替換,方法前仍然要加上dynamic,由於方法的替換也須要動態派發。

內存管理

循環引用

Swift使用ARC來自動管理內存,大多數狀況下開發人員不須要手動管理內存,但在使用ObjC開發時,你們都會遇到循環引用的問題,在Swift中也不可避免。 舉例來講,人員有一個身份證(Person有idCard屬性),而身份證就有一個擁有者(IDCard有owner屬性),那麼對於一個Person對象一旦創建了這種關係以後就會和IDCard對象相互引用而沒法被正確的釋放。

例以下面的代碼在執行完test以後p和idCard兩個對象均不會被釋放:

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執行完以後p和idCard均不會被釋放(沒法執行deinit方法)
test()

println("wait...")

兩個對象之間的引用關係以下圖:

CircularRefrence

爲了不這個問題Swift採用了和ObjC中一樣的概念:弱引用,一般將被動的一方的引用設置爲弱引用來解決循環引用問題。例如這裏能夠將IDCard中的owner設置爲弱引用。由於IDCard對於Person的引用變成了弱引用,而Person持有IDCard的強引用,這樣一來Person做爲主動方,只要它被釋放後IDCard也會跟着釋放。如要聲明弱引用可使用weak和unowned關鍵字,前者用於可選類型後者用於非可選類型,至關於ObjC中的__weak和__unsafe_unretained(由於weak聲明的對象釋放後會設置爲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執行完以後p會被釋放,其後idCard跟着被釋放
test()

println("wait...")

如今兩個對象之間的引用關係以下圖:

WeakRefrence

固然相似於上面的引用關係實際遇到的並很少,更多的仍是存在於閉包之中(ObjC中多出現於Block中),由於閉包會持有其內部引用的元素。下面簡單修改一下上面的例子,給Person添加一個閉包屬性,而且在其中訪問self,這樣閉包自身就和Person類之間造成循環引用。

import Foundation

class Person {
    let name:String
    
    //下面的默認閉包實現中使用了self,會引發循環引用
    lazy var description:()->NSString = {
        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...")
/**打印結果
name = Kenshin Cui
wait...
*/

Swift中使用閉包捕獲列表來解決閉包中的循環引用問題,這種方式有點相似於ObjC中的weakSelf方式,當時語法更加優雅, 具體實現以下:

import Foundation

class Person {
    let name:String
    
    //使用閉包捕獲列表解決循環引用
    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...")
/**打印結果
name = Kenshin Cui
Person deinit...
wait...
 */

指針與內存

除了循環引用問題,Swift之因此將指針類型標識爲「unsafe」是由於指針沒辦法像其餘類型同樣進行自動內存管理,所以有必要了解一下指針和內存的關係。在Swift中初始化一個指針必須經過alloc和initialize兩步,而回收一個指針須要調用destroy和dealloc(一般dealloc以後還會將指針設置爲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做爲參數進行inout參數傳遞,可是沒法直接獲取其地址,下面的作法是錯誤的
    //var address = &p
    
    /*建立一個指向Person的指針pointer*/
    //申請內存(alloc參數表明申請n個Person類型的內存)
    var pointer:UnsafeMutablePointer = UnsafeMutablePointer.alloc(1)
    //初始化
    pointer.initialize(p)
    
    //獲取指針指向的對象
    var p2 = pointer.memory
    println(p===p2) //結果:true,由於p和p2指向同一個對象
    //修改對象的值
    p2.name = "Kaoru"
    println(p.name) //結果:Kaoru
    
    
    //銷燬指針
    pointer.destroy()
    //釋放內存
    pointer.dealloc(1)
    //指向空地址
    pointer = nil
}

test()

println("waiting...")
/**打印結果

Kaoru
PersonKaoru deinit...
waiting...

*/

運行程序能夠看到p對象在函數執行結束以後被銷燬,可是若是僅僅將pointer設置爲nil是沒法銷燬Person對象的,這很相似於以前的MRC內存管理,在Swift中使用指針須要注意:誰建立(alloc,malloc,calloc)誰釋放。 固然上面演示中顯然對於指針的操做略顯麻煩,若是須要對一個變量進行指針操做能夠藉助於Swift中提供的一個方法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) //結果:Kaoru

在前面的C語言系列文章中有一部份內容用於介紹如何利用指針遍歷一個數組,固然在Swift中仍然能夠採用這種方式,可是在Swift中若是想要使用指針操做數組中每一個元素的話一般藉助於另外一個類型UnsafeMutableBufferPointer。這個類表示一段連續內存,一般用於表示數組或字典的指針類型。

import Foundation

var array:[String] = ["Kenshin","Kaorsu","Tom"]

//UnsafeBufferPointer和UnsafeMutableBufferPointer用於表示一段連續內存的指針,例如:數組或字典
//下面建立一個指向數組的指針
var pointer = UnsafeMutableBufferPointer(start: &array, count: 3)

//baseAddress屬性表示內存首地址
var baseAddress = pointer.baseAddress as UnsafeMutablePointer
println(baseAddress.memory) //結果:Kenshin

//利用指針遍歷數組
for index in 1...pointer.count {
    println(baseAddress.memory)
    //向後移動指針,向前移動使用baseAddress.predecessor()
    baseAddress = baseAddress.successor()
}
/**打印結果
Kenshin
Kaorsu
Tom
*/

 擴展—Core Foundation

Core Foundation做爲iOS開發中最重要的框架之一,在iOS開發中有着重要的地位,可是它是一組C語言接口,在使用時須要開發人員本身管理內存。在Swift中使用Core Foundation框架(包括其餘Core開頭的框架)須要區分這個API返回的對象是否進行了標註:

1.若是已經標註則在使用時徹底不用考慮內存管理(它能夠自動管理內存)。

2.若是沒有標註則編譯器不會進行內存管理託管,此時須要將這個非託管對象轉化爲託管對象(固然你也可使用retain()、release()或者autorelease()手動管理內存,可是不推薦這麼作)。固然,蘋果開發工具組會盡量的標註這些API以實現C代碼和Swift的自動橋接,可是在此以前未標註的API會返回Unmanaged<Type>結構,能夠調用takeUnretainedValue()和takeRetainedValue()方法將其轉化爲能夠自動進行內存管理的託管對象(具體是調用前者仍是後者,須要根據是否須要開發者本身進行內存管理而定,其本質是使用takeRetainedValue()方法,在對象使用完以後會調用一次release()方法。按照Core Foundation的命名標準,一般若是函數名中含「Create」、「Copy」、「Retain」關鍵字須要調用takeRetainedValue()方法來轉化成託管對象)。

固然,上述兩種方式均是針對系統框架而言,若是是開發者編寫的類或者第三方類庫,應該儘量按照Cocoa規範命名而且在合適的地方使用CF_RETURNS_RETAINED和CF_RETURNS_NOT_RETAINED來進行標註以即可以進行自動內存管理。

備註:

1.在Swift中內存的申請除了使用alloc其實也可使用malloc或者calloc,此時釋放時使用free函數;

2.關於更多內存管理的內容能夠參見前面的文章:iOS開發系列http://www.cnblogs.com/kenshincui/p/3870325.html

相關文章
相關標籤/搜索