iOS編程時, 實際是進行Cocoa編程. 所以必須熟悉Cocoa, 必須知道Cocoa是什麼, 它能夠做什麼, 你和Cocoa如何進行”交流”.
Cocoa是一個龐大的Framework, 被分割成若干較小的Framework. 任何iOS編程人員都需要花費一定時間來熟練Cocoa.
Cocoa中含有一些主要的規則和組件, 最好是以它們為主線來學習Cocoa.
Cocoa大部分類都是OC寫的, 雖然OC類和Swift類能相互轉換.但Swift中的Enum和Struct和OC中的不兼容. 不過, 一些重要的Swift對象都能橋接到Cocoa類.
本章主要介紹Cocoa如何組織, 即它的組成, 然後說明一些常用類用法, 最後介紹NSObject類.
Swift中自定義類的方式有多種:
繼承 類擴展 協議Cocoa中提供的類若無法滿足需求時, 可以進行自定義.
但首先要對Cocoa中的類進行全面了解, 因為有的功能並不是沒有,而只是沒找到. 所以請熟悉類相關的文檔.
某些類總是會被繼承, 比如UIViewController類.
另一個例子是UIView, 許多類都繼承自UIView, 比如UIButton, UITextField等.
如果要添加額外繪制行為, 可以重寫drawRect:
方法. 過程是先繼承UIVIew, 然後在子類中實現drawRect:
方法即可.
比如要在window中繪制一條水平線, 因為Cocoa中沒有繪制水平線的類, 故可自定義UIView的子類, 讓它繪制一條水平線即可.
方法如下所示:
新建工程並新建一個類, 命名為MyHorizontal, 這個類繼承自UIView. 修改MyHorizontal.swift如下所示:class MyHorizontal: UIView { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.backgroundColor = UIColor.clearColor() } override func drawRect(rect: CGRect) { let c = UIGraphicsGetCurrentContext() CGContextMoveToPoint(c, 0, 0) CGContextAddLineToPoint(c, self.bounds.size.width, 0) CGContextStrokePath(c) } }
3.在storyboard中的VC控制的scene中的View內再添加一個view,並將這個添加的view類型修改為MyHorizontal
4.運行程序,可以看到需要的線已經畫出來了, 如下圖所示:
上述代碼中,繼承自UIView的類繪制了一條水平線, 由於UIView本身沒有其他繪制行為,故在
drawRectangle:
方法中沒有調用super.drawRect:
.
UIView的子類UILabel中有兩個方法:
3.在View中添加一個Label控件,並將它類型修改為MyBoundedLabel,運行程序如下所示: 實際工作中用繼承方式來自定義子類的情況並不多見(在Cocoa框架內), 雖然有時使用繼承的確比較方便. 原則:除非Cocoa需要你使用繼承來自定義行為,否則不應該使用. 絕大多數Cocoa Touch類都不需要進行繼承(有些在文檔中明確表示不能繼承), 因為有Delegation的存在. 推薦使用委托來添加自定義行為. 比如UIApplication對象, 它的許多行為都轉交給AppDelegate對象進行代理. 而AppDelegate類並不是繼承自UIApplication類,而是接受了UIApplicationDelegate**協議**. Category在OC中表示類擴展(具名類擴展),而Cocoa中大量使用Category對類進行組織. 相當於是Swift中的Extension. 利用Extension, 可將類方法或對象方法注入到類中. Swift中廣泛使用extension,原因有兩點: 比如在Swift.h頭文件中,使用了許多Extension添加屬性或行為. 頭文件中Array的Extension: 代碼中的Extension為協議擴展. 協議擴展在功能上講是完全沒有必要的, 因為可以在Array類中將這些全部寫進去. 但為了便於理解, 故按照邏輯功能將類的方法或屬性劃分成一個個獨立的擴展. Swift中允許定義全局函數, 這樣做沒有什麼錯. 但卻不滿足OO編程中對封裝的要求, 故更好的方式是將函數寫到類中去. 假如僅僅為了添加幾個方法而去繼承一個龐大的類,這樣的做法往往得不償失, 並且很可能不能幫助你完成想完成的任務.(比如想添加一個函數處理多種不同類型, 如果使用繼承, 可能這個方法就只能處理該類對象,而其他類對象的處理又必須重新定義方法), Extension還可用在Enum, Struct上, 而繼承只能在Class上使用. 而且還有個好處, 如果將某方法用Extension插入到類中, 該類的全部子類都自動獲得該方法. 比如要讓一個UIButton和UIBarButton都具有某個行為,可以聲明具有該行為的Protocol, 然後在Protocol的extension中實現該方法, 那麼,這兩個類只需要接受Protocol,便自動擁有了該方法,而無需自己實現. 這樣的Extension就是協議擴展. 這個辦法可用於為若干類統一添加行為. 總結一下添加自定義行為的兩個辦法: 定義某個類的Extension: 協議擴展 Cocoa使用Category對類進行組織. 將類按功能分割為若干組成部分, 每個部分便對應一個Category, 並且每個Category對應一個頭文件. 這樣的方式會對文檔查詢造成影響. 比如文檔中對NSString類聲明只說了三個文件: NSString.h, NSPathUtilities.h和NSURL.h, 但NSStringDrawing.h卻沒有提到(而是在NSString UIKit Addtions中), 不得不說這是Cocoa文檔瑕疵所在. OC的Protocol和Swift的Protocol可以相互轉化, 而且在Swift中被標記@objc的protocol可以被OC使用. 例如Cocoa中的對象復制時, 有的對象可被復制, 而有的卻不能. 因為Cocoa中定義了一個復制對象協議: NSCopying. NSCopying在NSObject.h中實現, 但NSObject沒有接受這個協議. 接受這個協議的類可以使用它: 比如遵守某個協議的屬性: 意思是:不管dataSource是什麼類型的,只要它是**接受**UITableViewDataSource協議的類型對象,就可以賦值給dataSource屬性. 這裡接受的意思是類聲明中包含該協議, 並且類中實現了required方法. 協議的另外一個場景: 代理協議. 比如在UIApplication中有一個屬性: 這個屬性用於指定它的代理對象. Cocoa中協議都有其單獨文檔, 列出協議內的方法. 比如上面代碼中, UIApplicationDelegate就是一個協議. 當一個類接受某協議之後,它的行為變得更多了,這時不僅要關注類的行為,還要關注協議中聲明的行為.並且,還需要關注它父類行為和父類接受的協議所規定的父類的額外行為… Informal Protocol並非真的指協議,它是給編譯器提供方法聲明的一種途徑,好讓編譯器不再抱怨. 實現Informal Protocol的兩種途徑: 這種技術之前用得很多,不過自從protocol中有了optional方法之後就替代了它. 但是在Cocoa中還有少部分使用Informal protocol的情況. 所有的OC協議,以及在Swift中用@objc聲明的協議,都可以擁有Optional方法. 當對象接收一個它無法處理的消息時(它裡面沒有這個方法), 那就會出錯,並拋出異常. 但如果類中有這個方法的聲明時, 表明這個類有處理這個消息的能力,只是還沒有實現. 這樣就可以避免出現異常(這就是Informal protocol的用途, 也即Optional的作用) OC中調用 比如下面的協議: 假如某類型接受該協議, 向這種類型對象發送 當使用Optional方法的時候會調用 在開始正式編程之前,需要了解一些常用的類, 詳見 Foundation Framework Reference. NSRange: 它是一個C結構體, 具有兩個屬性: location和length. 比如location是1, length是2, 表示第零個元素和第一個元素. 使用NSMakeRange創造NSRange. Swift裡面有Range Struct, 可以將NSRange轉換為Swift中的Range,方法是: NSNotFound是一個整形常量,用於指示未找到的情況. 許多的查找方法都會返回NSNotFound,比如下面的: 如果找到的話, indexOf方法就會返回一個可選值包裝的Int, 否則就是nil. 如果返回的是NSRange的話,那麼location的值就會是NSNotFound. Swift可以自動處理轉換問題,使得用戶不必擔心與NSNotFound比較會出問題. 比如NSString的 這樣的好處是:假如你需要一個Swift中的Range, 可以用這個NSRange轉換出一個Range; 假如你需要一個NSRange, 那直接使用即可, 它可以直接同Cocoa交互. NSString是Cocoa中的字符串, NSString被橋接到Swift的String, 它們二者可以自動轉換. 可將Swfit的String作為參數傳遞給需要NSString的函數,也可以在String上面使用NSString的方法等等. 比如 轉換過程是: 在s上調用 這樣的好處是可以直接在String上直接使用NSString方法,但如果你沒有 但有時轉換無法自動完成,比如: 解決辦法都是先將s轉換為NSString再調用即可: 具體的原因是由於NSString和String在元素的表示上面存在差異造成的, 詳見蘋果String Programming Guide. 還有一個注意點就是NSString是不可變的,要使用它的可變版本,則用NSMutableString. 比如: 在以後的編程中,經常會需要在String和NSString的”連接橋”上過來過去,但多數情況是在NSString這邊, 因為許多優秀方法都在Cocoa裡面, 比如字符串搜索: 下面的例子裡,嘗試將文本中所有的”hell”替換成”heaven”,並且不想將hello中的hell替換: NSString也可以很方便地表示文件路徑, 它經常和NSURL結合使用. NSString和其他一些類,都提供了讀寫文件的方法, 文件可以用NSString表示的文件路徑指定,也可以用NSURL指定. NSString裡面不含文字樣式信息,如果想使用文字樣式, 則利用:NSAttributedString, NSParagraphStyle, NSMutableParagraphStyle. 這些類允許自定義文本或段落風格. 同時內置的UI對象可以顯示帶風格的文本. NSString以及NSAttributedString上的NSStringDrawing擴展支持字符串繪制.詳見String UIKit Additions Reference和NSAttributedString UIKit Addtions Reference. 通俗來說,NSDate就是表示日期和時間, 內部以秒(NSTimeInterval)表示距某日期的間隔. 調用NSDate的構造函數構造出來的NSDate對象表示當前日期和時間: 許多日期操作都涉及NSDateComponents類的使用,如果想要在NSDate和NSDateComponents之間轉換,需要使用NSCalendar作為中間媒介. 一般使用日歷來構造NSDate: 同時,如果想要對日期進行計算的話, 則可NSDateComponents: 代碼中構造先當前日期.然後構造一個日期組件,只設置它的月份.然後使用NSCalendar作為中介,在當前日期上面加一個日期組件的值,得到日期d2. 可以將日期表示為字符串, 默認是0時區的. 如果想獲得當前時區中時間,可以使用如下方式: 兩條語句的輸出結果為: 為解析日期字符串,使用NSDateFormatter, 它使用類似NSLog的格式控制字符: 代碼中創建一個NSDateFormatter對象df,它用於解析日期字符串.然後構造一個自定義的format,這個format的作用就是設置df格式.然後調用df.stringFromDate,即可獲得指定格式的字符串. 如果想逆向解析這個字符串,只需使用相同的format設置,然後使用NSDateFormatter的DateFromString方法解析即可. NSNumber是一個包含數值的對象. 裡面的數值可以是任何OC原子數值類型. 由於OC原子類型並非對象, 所以這樣的數值不能用在需要對象的場合中. 但利用它可以將Swift中數值對象轉換為原子類型數值. Swift中為了避免用戶同NSNumber直接接觸, 將數值對象同OC的數值進行了橋接: 如果需要的是原子數值(即不是對象), 則將Swift中的數值(對象)轉換為原子數值 如果需要的是數值對象, 則將Swift的數值對象自動轉換為NSNumber.可以進行自動轉換的類型有:Int, UIInt,Float, Double, Bool, 下面就是自動進行橋接轉換的例子: 只看第3行和第4行: 這兩行的i就是不同方式的轉換, i是Swift數值對象, 它在第3行被轉換為一個原子數值,而在第四行被轉換為NSNumber對象. 如果想在Swift中進行顯式轉換, 可以使用下面的方式: 值從OC返回Swift時, 大多數情況返回的都是AnyObject, 此時需要進行類型轉換. NSNumber僅僅作為一個數值的容器而已,如果需要裡面的數值,還需要手動將數值”解壓”出來. NSNumber的子類NSDecimalNumber支持計算, 但僅限兩個相同NSDecimalNumber之間,比如: NSDecimalNumber經常被用在取整上面, 因為它提供了許多方便的方法. NSDecimalNumber裡面實際是包含一個NSDecimal結構體(對應decimalValue屬性),這是一個C結構體. NSDecimal結構體的函數比NSDecimalNumber中的方法速度更快. NSValue是NSNumber的父類, 它用於包裝非數值類型的C值, 比如結構體. 在Swift中無法使用C的結構體, 利用NSValue就可以解決這一問題. 經常用NSValue來包裝以及解包CGPoint, CGRect, CGSize等結構體, 以及NSRange, CATransform3D, CMTime等. 通常不需要手動將C結構體保存在NSValue中,但是如果你想的話,是可以進行的. 因為Swift不會自動將C結構體裝進NSValue中, 需要你手動完成操作. 另外我們可以將CGPoint裝進Swift數組中, 因為CGPoint是Swift結構體(也是對象), 而Swift數組可以存放任何對象.但是在OC中卻不行,因為OC數組只能存放對象, 所以在OC中先將CGPoint裝進NSValue, 再放入數組中的. NSData實際是一串字節,或說它是一個緩沖池或一塊內存區域. NSData是不可變的, 不過它有可變子類 NSMutableData. 在實際工作中, 有兩大情況需要使用NSData: 當從網絡下載數據時. 例如NSURLConnection或NSURLSession可以將任何從網絡獲取的數據保存在NSData中. 假設獲取的是一些字符串, 則只要指定正確的解碼方式,就可以將字符串從NSData中解析出來. 當將對象保存到文件或用戶配置(NSUserDefaults)中時. 例如無法直接將一個UIColor保存到用戶配置中去, 當用戶選擇一個顏色設置並保存的時, 先將UIColor轉換成NSData(使用NSKeyedArchiver), 然後再保存: 代碼中首先獲得用戶配置ud,然後構造一個cdata(從顏色對象c)它是NSData類型的, 構造cdata使用的是NSKeyedArchiver的 在Swift中, 操作符可以在類中覆蓋定義(類似C++的運算符重載), 並且使用infix,postfix,prefix分別表示二元,一元前綴,一元後綴運算符: 但OC中運算符不支持重載, 要比較兩個對象, 比如說判等需要覆蓋實現 而Swift中將NSObject類或它的子類看作可比的, 比較時自動將”==”操作隱式轉換成 上面代碼無錯的原因有兩點: NSNumber裡面已經實現了isEqual, 所以可以直接使用”==”操作符. 但如果某個NSObject子類沒有實現isEqual, 則會執行NSObject中的isEqual, 即比較兩個對象是否是同一個, 類似於Swift中的”===”操作符. 代碼中的Dog類,繼承自NSObject,但沒有實現isEqual, 則判等為假. OC對象更多地是使用比較函數來進行比較, 如NSNumber的 OC中對象的大小比較則要看具體類中是否有相應實現了.標准的比較方法是 即接收對象比參數對象小 即接收對象和參數對象相等 即接收對象比參數對象大 Swift不會自動調用compare方法, 即如果代碼中出現兩個NSObject或其子類對象比較大小的情況, 直接使用”>”這類比較操作符是不行的. 比如下面的代碼會出錯: 此時需要做的就是顯式調用compare方法: 代碼中n1為1,n2對象為2, n1的compare方法返回的是n1小於n2對應的NSComparisonResult,即 NSIndexSet是數值集合, 主要用於表示關系集合中元素的下標. 比如想同時訪問數組中多個對象,可以將這些對象下標先全部存放到一個NSIndexSet中. 可以傳遞一個NSIndexSet給UITableView指示在哪些section中插入或刪除元素. 假設需要訪問數組中下標為 1, 2, 3, 4, 8, 9, 10的元素, 可以先將這些下標存放在NSIndexSet對象中. 同樣, NSIndexSet是不可變的, 它有一個可變子類 NSMutableIndexSet. 可以傳遞NSRange以構造NSIndexSet, 但下標情況復雜時, 可以使用NSMutableIndexSet, 利用append添加NSRange進去: 代碼中首先構造一個NSMutableIndexSet對象ixs, 然後將該對象附加兩個范圍, 之後通過這個ixs指定的下標構造新數組. 可以使用 NSArray是OC數組類型, 和Swift數組在功能上類似, 並且NSArray和Swift數組也進行了橋接. 與Swift數組相比, NSArray裡面必須存放對象,並且對象類型可以不統一. NSArray同Swift數組相互轉換的方法詳見書上. TIP: 在iOS9中, 如果NSArray裡面保存的對象都是同一種類型的,則可以在OC中標明該數組的類型.這樣Swift中就可以直接讀取該數組類型. 通過這樣,從OC橋接回Swift就不會再收到一個[AnyObject], 而是一個實在的數組. 這同樣適用於NSSet, 以及少部分NSDictionary. NSArray的長度在count屬性中,可以通過 除了使用 NSArray能夠使用下標操作符, 並非因為它橋接到了Swift, 而是它裡面實現了 可以使用 NSArray是不可變的,即不可以改變它裡面保存的元素,但對於在每個元素內部的修改,NSArray是沒有限制的. NSArray有可變子類NSMutableArray, 使用它可以動態增減元素. Swift的Array沒有橋接到NSMutableArray. 如果從OC回到Swift, NSMutableArray也是一個[AnyObject], 但它不能直接轉換成Swift的Array, 需要首先將它轉換成NSArray, 然後再轉換成對應類型的Swift中Array: 可以使用block在數組中查找或過濾元素, 還可以對數組排序, 只需指定排序規則. 對於可變數組而言, 直接可以進行排序. 當然在Swift數組中可以很方便實現這樣的操作,但了解如何在Cocoa中進行也是非常重要的: 上面就是過濾並排序之後輸出數組的例子. NSDictionary是OC中的字典類型, 在功能上和Swift中的字典類似, 它們二者也已被橋接. NSDictionary的鍵和值都必須是對象, 鍵值對類型不必統一, 這和Swift中不一樣. 作為鍵的對象必須接受NSCopying協議, 並且可散列. NSDictionary和Swift字典的橋接及轉換詳見書上. NSDictionary是不可變的, 它有可變子類NSMutableDictionary, 並且Swift字典沒有橋接到NSMutableDictionary. 要構造一個可變字典, 可以直接使用它的構造方法: NSDictionary的鍵可以用isEqual來判別比較. 如果你在可變字典內添加一個鍵值對, 若該鍵在字典中還未存在, 那麼就直接將這個鍵值對加入到字典中. 但如果鍵已存在, 則會用新值覆蓋對應鍵的值, 這和Swift字典的行為類似. 字典的最基本使用是通過鍵來獲取值, 比如使用 總的來說, 因為Cocoa大部分是OC寫的, 所以需要遵守一些OC規則. Swift處理 在NSDictionary或NSMutableDictionary中都可以使用下標來訪問鍵對應值, 和在NSArray中使用下標操作的道理類似. 在NSDictionary中實現了 可以獲取NSDictionary中的全部鍵的列表或全部值的列表, 或按值排序的鍵值對列表. 也可以使用Block來遍歷鍵值對, 甚至可以通過測試來過濾NSDictionary中的特定值. NSSet是一個無關的互異對象集合. 它裡面的對象都是互異的, 即任意兩個對象使用 在NSSet中的查詢操作比數組中的查詢操作效率更高, 並且對於一個Set, 可以查詢它是否是某Set的子集, 或它與另一Set的交集. 使用 可以過濾一個Set, 就和過濾Array類似. 可以看出, Set上的大部分操作都和數組類似, 當然在Set上無法進行需要元素有序為前提的操作, 比如下標操作. 可以使用NSOrderdSet來構造一個關系集合. NSOrderedSet和數組十分相似, 操作它的方法和數組也類似, 比如可以使用下標訪問其中元素. NSOrderedSet比數組具有更多優勢, 比如查詢效率上, 另外就是它可以直接進行兩個NSOrderedSet之間的並集,交集,差集操作. 故在條件允許的情況下, 可以盡量使用NSOrderedSet. TIP: 將一個數組傳給NSOrderedSet, 意味著順序仍然得以維護, 但只有互異的元素被傳入到集合中.(即自動進行了一次去重操作, 順序仍和數組中的一致) NSSet是不可變的. 但可以通過從其他NSSet添加或刪除元素得到新的NSSet. NSSet有可變子類NSMutableSet. 當然NSOrderedSet也有其可變子類NSMutableOrderedSet(使用它可以用下標訪問元素,因為其中實現了 向Set中加入已存在的元素不會出錯, 只是添加操作不會發生. NSMutableSet還有一個子類NSCountedSet, 這個子類是可變的, 並且元素允許重復, 它經常被稱為bag. NSCountedSet的實現其實就是一個Set外帶記錄每個元素出現次數的計數器. Swift中的Set被橋接到NSSet. 但NSSet中元素必須是對象(類對象或類的實例對象), 而且元素類型不必統一. NSMutableSet, NSOrderedSet, NSMutableOrderedSet, NSCountedSet都沒有被橋接到Swift. 但可以先將NSMutableSet向上轉換為NSSet, 然後再轉換為Swift中Set(和NSMutableArray的轉換類似). NSOrderedSet從表面上看和Swift中的數組或set並無二致, 因為它們的行為基本相同, 但是建議不要輕易將NSCountedSet或NSOrderedSet轉換到Swift中, 能讓它們留著OC世界就盡量留在OC世界. NSNull的作用就是提供一個指向單例(NSNull對象)的指針,使用 某些情況下需要OC對象, 但又不允許使用nil, 此時就使用NSNull代表nil. 比如在OC的任何一種集合中都無法添加值為nil的元素, 因為nil本來不是對象, 這時為了代表nil, 就使用NSNull()來添加”意義上是nil的元素”對象. 可以在NSNull單例對象上使用”==”操作符, 它會自動調用NSObject裡面的 Cocoa中通常都是一個不可變類擁有一個可變子類的類組織方式. 不可變類和可變類就好比Swift中的let和var的區別. 比如使用NSArray, 就和在Swift中使用let定義一個數組的使用方式相同: 用戶不能向這種數組中添加元素, 刪除元素以及替換元素, 但如果獲取到其中的元素, 在元素身上進行的修改, 數組是無權控制的. 而Cocoa框架中使用不可變/可變這樣的類組織, 目的就是為了防止未授權的訪問. 比如一個數組, 可以先在內部暫時使用它的可變子類, 當需要傳遞出去時, 則使用不可變的數組. 這樣可以防止在外部的修改. 而Swift不存在這樣的情況, 因為內部的對象如String, Array等, 修改它們的唯一方式就是創建副本並賦值新修改的副本, 這樣的話就可以被setter observer自動檢測到. 所以Swift中不用擔心修改不被察覺的問題. 用戶可以使用 Warning: 這種不可變/可變類組織方式, 實際是由類簇(class cluster)實現的, 即用戶使用的類只是一個接口, 而實際實現的類被隱藏在接口層次的下面. 不必去關系這些作為接口的類下面的實現類的細節. 想關心也關心不了. Property List實際是字符串(XML), 用於存放數據. 只有如下幾種類才可以被轉換為Property List: NSString, NSData, NSArray, NSDictionary. NSArray或NSDictionary轉換成Property List的條件是: 包含的對象必須都是NSData或NSNumber類型的(這也是為什麼在UIColor存儲的例子裡, UIColor必須先轉換為NSData然後才可以存放到User Default中, 因為User Default也是Property List). Property List的一個重要作用是將數據存儲到文件中, 當需要用這些數據時, 還可以再次重構到某對象中. NSArray和NSDictionary中 NSData和NSNumber中也提供了文件讀寫方法, 只是這些方法將對象數據直接寫入文件(即寫入的不是XML), 而非生成property list然後寫入文件. 當由property list文件生成對應數組或字典對象時, 內部包含的字符串或數據都是不可變的. 如果想讓它們可變, 或如果想將一個Property list對應的對象轉換為另一類型的property list(比如字典Property list 和數組Property list的轉換), 可以使用 OC實例變量: 指這個OC變量引用的是一個對象(或說成是這個變量存放的是一個對象). Swift實例屬性: 指這個Swift屬性引用的是一個對象. OC中的實例變量和Swift中的實例屬性類似: 它是一個變量, 這個變量是特定類型類的實例,即對象. 但OC的實例變量通常都是私有的, 即一個類看不到另外一個類的實例變量, Swift類也看不到OC類裡面. 如果想讓外界訪問實例變量, 這個類就需要實現accessor方法: 一個setter和一個getter. 並且OC中的accessor方法有特定命名規范: OC提供了 則frame實例變量自動擁有accessor方法: getter為 當Swift遇到OC中的@property的時候, 會自動將它等價為Swift中的屬性, 上面的frame在Swift中即: OC的屬性名實際是一個語法糖, 比如設置UIView的frame屬性 ,使用的是 OC中可以直接調用 OC的屬性修飾符中有 因為OC中屬性名作為訪問方法的”快捷方式”, OC將Swift屬性名也作為這種”快捷方式”使用, 即使沒有實現對應的訪問方法. 比如你在Swift類中定義了一個屬性名為prop, 則在OC中你可以直接使用 在Swift中, 不需要顯式實現訪問方法, 如果你嘗試去實現, 編譯器會提醒錯誤. 如果想實現這樣的訪問方法, 則使用computed property, 比如要設置ViewController的Color屬性(computed property),在Swift中有如下定義: 則在OC中可以顯式調用這個屬性的accessor方法, 並且滿足命名規范: 上面代碼表明, 在Swift中使用computed property, OC會認為你實現了accessor方法. 甚至可以在Swift中改變accessor名稱, 方法是將@objc(替換名)寫到屬性前面: 這樣, 當OC中調用這個屬性的accessor時, 就使用替換後的名稱: getter是 另外可以在setter中添加額外操作, 比如想在UIView的子類中對應的OC的setFrame:方法中添加額外行為: Cocoa框架提供了運行時動態調用accessor的途徑, 即在運行時通過字符串Key指定需要訪問的屬性Value, 這就是常說的Key-Value Coding(KVC)機制. 這個機制的原理和通過selector名稱調用 指定的字符串就稱為Key, 被Key定位之後, 可以調用該屬性的setter或調用該屬性getter, setter或getter訪問的數據稱為Value. KVC的基石是 如果Swift類對象要使用KVC, 則該對象必須屬於NSObject類家族. KVC中最基本的方法有兩個: 另外兩個常用方法是: KVC中的Value必須是OC對象, 在Swift看來就是[AnyObject]. 當調用 說某個類是KVC兼容(Key-Value Coding compliant)是指這個類提供了對應key的訪問方法, 或擁有對應key的實例變量. 如果不想崩潰, 應該怎麼做呢? 做法即保證該類中實現了對應key的accessor, 或具有對應key名稱的實例變量. 比如上面例子中, 需要有一個setter方法: 需要強調的是: 在Swift中實例屬性就提供了accessor方法. 因此, 我們可以在任何繼承於NSObject的類的Swift對象上面使用KVC, 只需要保證其中有Key對應的屬性即可. 比如: 雖說使用KVC和OO封裝思想背道而馳, 但KVC在iOS編程中還是有用的, 尤其是在Cocoa中有特殊用途, 比如: KVC為Outlet提供幕後支持. nib中的Outlet名稱是一個字符串, KVC將該名稱作為Key來定位到對應的對象屬性上面. 假如你有一個Dog類, 它有個Outlet屬性master, 你將這個屬性聯系到nib文件中的person對象上. 當person對象從nib加載時, outlet的名稱master被KVC用於調用accessor方法 如果nib文件中的Outlet名稱和類中屬性名不對應, 則會在運行時出錯. 運行時, 當nib加載後, Cocoa會嘗試利用KVC來設置nib對象到你的一個對象屬性上面, 如果名稱不對應, 則會設置失敗. 結果是出現異常, 程序會在nib加載時崩潰. 這個錯誤常見於先配置好Outlet, 然後不小心修改了屬性名卻沒有修改Outlet名的情況. key path允許用戶使用一條語句進行鏈式訪問. 若對象兼容某個Key的, 而這個key對應的屬性又是一個對象且對另外一個key是KVC兼容, 就可以將這些key用在key鏈上. 使用 可以將KeyPath看作是一個字符串,只是每個key用點號分割, 如 例如有這樣一個KeyPath: 比如myObject中有一個theData屬性: 這個屬性本身是一個字典數組, 可以使用KVC, 並利用KeyPath來獲取一個新數組: 上面代碼中的KeyPath首先找到theData屬性, 然後從這個屬性中獲取全部的名字, 然後構成一個新數組(原理請看5.3第一條). TIP: nib中可以設置user-defined runtime attributes. 這個功能也是利用KVC. 而它的第一列所有的key都可以連成一個KeyPath. KVC是一個強大的技術, 並且它還有許多衍生技術.(詳見蘋果Key-Value Coding Programming Guide). 因為所有OC類都繼承自NSObject, 故有必要單獨討論一下NSObject. NSObject的構造十分精巧: 如果想知道某個類能夠接收什麼消息, 不用關心這個消息在何處聲明, 你只需要弄清楚可以向這個對象發送哪些消息. 但這裡出現了一個問題, 即蘋果的文檔不是以”可以向這個類型的對象發送什麼消息”來組織的, 而是以”這個方法在哪裡”的形式來組織的. 這樣就導致了一個對開發者很不友好的情況發生: 即使基類NSObject, 文檔中都沒有一個對它的完整的描述, 而是需要同時查找NSObject Class Reference和NSObject Protocol Reference, 外帶NSCopying, NSMutableCopying, NSCoding的幫助文檔, 再加上對每個NSObject對象方法對應的類方法版本的自我理解. 另外還有一些方法是以類擴展形式插入到NSObject中的, 有一些方法列在了NSObject的類描述文檔中, 但有些需要列出的卻沒有被列出, 只有在實際使用時去找. 不管來偷的還是用搶的, 只有將所有的NSObject方法都湊齊的時候, 才能真正知道NSObject的全貌: 構造, 析構以及內存管理: 用於創建對象的方法, 如alloc和copy, 另外還有一些關於對象生命期的方法, 如initialize何dealloc, 以及內存管理方法. 類關系描述: 用於確定對象的類和繼承關系, 比如superclass, isKindOfClass及isMemberOfClass. 對象自回應和比較: 用於查詢當發送某消息時對象的反應, 如respondsToSelector, 用字符串描述對象 消息響應: 向對象發送消息後用於干預對象行為的方法, 如 消息發送: 用於動態發送消息. 比如 TIP: performSelector在Swift2.0之後才可用. 之前這個方法不能被Swift調用. 在實踐尋找替代思路的過程中發現, 不使用這個方法也能很好將OC代碼轉移到Swift上.drawTextInRect:
和textRectFZ喎?/kf/ware/vc/" target="_blank" class="keylink">vckJvdW5kczpsaW1pdGVkVG9OdW1iZXJPZkxpbmVzOjwvY29kZT48L3A+DQo8cD7I57n7z+vX1Lao0uVVSUxhYmVsLCC/ydLU1NrL/NfTwODW0LiyuMfV4tCpt723qC4gtbG75tbGVUlMYWJlbMqxLCDV4sG9uPa3vbeou+Gxu9fUtq+199PDLjwvcD4NCjxwPr/J0tTKudPDPGNvZGU+ZHJhd1RleHRJblJlY3Q6PC9jb2RlPre9t6jF5NbDz9TKvjo8L3A+DQo8YmxvY2txdW90ZT4NCgnQwr2ouaSzzCwg0MK9qNK7uPZVSUxhYmVstcTX08DgLCDD/MP7zqogTXlCb3VuZGVkTGFiZWwg1NpNeUJvdW5kZWRMYWJlbC5zd2lmdNbQvNPI68jnz8K0+sLrOg0KCTxwcmUgY2xhc3M9"brush:java;">
class MyBoundedLabel: UILabel {
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
CGContextStrokeRect(context, CGRectInset(self.bounds, 1.0, 1.0))
super.drawTextInRect(CGRectInset(rect, 5.0, 5.0))
}
}
2 Categories and Extensions
2.1 How Swift Uses Extensions
extension Array : CustomStringConvertible, CustomDebugStringConvertible {
/// A textual representation of `self`.
public var description: String { get }
/// A textual representation of `self`, suitable for debugging.
public var debugDescription: String { get }
}
extension Array {
/// Call `body(p)`, where `p` is a pointer to the `Array`'s
/// contiguous storage. If no such storage exists, it is first created.
///
/// Often, the optimizer can eliminate bounds checks within an
/// array algorithm, but when that fails, invoking the
/// same algorithm on `body`'s argument lets you trade safety for
/// speed.
public func withUnsafeBufferPointer
2.2 How You Use Extensions
protocol ButtonLike {//協議聲明
func behaveLikeAButton()
}
extension ButtonLike {//協議extension,即實現
func behaveLikeAButton {
//...
}
//在使用時,只需聲明即可, 用協議擴展類.
extension UIButton : ButtonLike {}
extension UIBarButton : ButtonLike{}
}
extension UIViewController { //直接擴展類添加方法
func saySomething {
print("hello")
}
}
//...
//使用時,比如在ViewController的viewDidLoad方法中:
self.saySomething() //輸出hello
protocol someBehavior {
func saySomething()
}
extension someBehavior {
//這裡理解為擴展該協議,實際和擴展類的道理一樣,反正extension中必須有實現
//找到了,如果想分別定義不同行為,又想接受統一個protocol,則這裡留空,然後在每個聲明中去實現.
func saySomething() {
print("good morning")
}
}
//...
//使用時,先這樣聲明,這個語法必須記住:
extension UIViewController : someBehavior {} //聲明這個類接受協議擴展
extension UIView : someBehavior {}
//...
//然後就可以使用了,比如在UIViewController的子類ViewController中:
self.saySomething() //輸出good morning
2.3 How Cocoa Uses Categories
3 Protocols
let s = "hello".copyWithZone(nil)
weak var dataSource : UITableViewDataSource?
unowned(unsafe) var delegate : UIApplicationDelegate?
3.1 Informal Protocols
3.2 Optional Methods
respondsT:Selector:
來判斷某對象能否響應某方法, Swift也使用類似方法.
@objc protocol Flier {
optional var song : String {get} //只讀屬性
optional func sing()
}
sing?()
消息, 系統會自動調用respondsToSelector:
判斷對象能否處理該消息, 然後再決定是否發送該消息給對象(這樣便不會出現異常).respondsToSelector:
, 有一部分額外開銷.4 Some Foundation Classes
4.1 Useful Structs and Constants
let r = NSRange().toRange() //可選類型的Range.
let arr = ["hey"] as NSArray
let ix = arr.indexOfObject("he")
if ix == NSNotFound {
print("it's not here.")
}
rangeOfString:
返回值是NSRange, 如果NSRange中的location值是NSNotFound的話, 那麼對應Swift的Range值就是nil:
let s = NSString(string: "hello world")
let y = s.rangeOfString("good").toRange() //y的值為nil.
let s = "hello" as NSSring
let r = s.indexOfString("ha") //這樣r即為一個NSRange
if r.location == NSNotFound {
print("it wasn't found")
}
4.2 NSString and Friends
let s = "hello"
let s2 = s.capitalizedString
//capitalizedString 返回NSString,但s2為HELLO,是Swift的String
capitalizedString:
方法時, 先將它自動轉換為NSString, 然後調用方法, 方法返回一個NSString, 再被自動轉換成String賦值給s2.import Foundation
, 則會出錯(但模擬不了…).
let s = "my"
let s2 = s.stringByAppendingPathExtension("text") //報錯
let s3 = s.substringToIndex(1) //報錯
let s2 = (s as NSString).stringsubstringToIndex(1)
let s3 = "abcdefg" //s3是Swift的String
let s4 = NSMutableString(string: s3) //s4是NSMutableString
s4.deleteCharactersInRange(NSMakeRange(1, 3))
let s5 = (s4 as String) + "hh" //s5 is a Swift String
rangOfString:
方法, 比如忽略大小寫,從尾部搜索等. 當自己都無法確定當前要搜索什麼的時候, 可以使用一定的結構來描述它,如使用NSScaner
. 指定選項.RegularExpressionSearch
支持以正則表達式搜索, 另外正則表達式也可以是單獨的類NSRagularExpression
,在裡面使用NSTextCheckingResult
來描述匹配結果. 有復雜文本自動分析支持,比如使用NSDataDetector
, 它是NSRegularExpression
的子類,可用於高效搜索如URL或電話號碼. 還有NSLinguisticTagger
,用於文本語法分析.
let s = NSMutableString(string: "hello world, go to hell")
let r = try! NSRegularExpression(pattern: "\\bhell\\b", options: .CaseInsensitive)
r.replaceMatchesInString(s, options: [], range: NSMakeRange(0, s.length), withTemplate: "heaven")
// s 現在變成了 "hello world, go to heaven"
4.3 NSDate and Friends
NSDate()
let greg = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let comp = NSDateComponents()
comp.year = 2016
comp.month = 8
comp.day = 10
comp.hour = 15
let d = greg.dateFromComponents(comp)
//上面的代碼中先構造一個日歷grep,然後構造一個日期組件comp,設置日期組件,再使用grep的datgeFromComponents方法來獲得NSDate
let d = NSDate()
let comp = NSDateComponents()
comp.month = 1
let greg = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let d2 = greg.dateByAddingComponents(comp, toDate: d, options: [])
print(d)
print(d.descriptionWithLocale(NSLocal.currentLocale()))
2016-07-21 08:21:55 +0000
Thursday, July 21, 2016 at 4:21:55 PM China Standard Time
let df = NSDateFormatter()
let format = NSDateFormatter.dateFormatFromTemplate("dMMMMyyyyhmmaz", options: 0, locale: NSLocale.currentLocale())
df.dateFormat = format
let s = df.stringFromDate(NSDate())
4.4 NSNumber
let ud = NSUserDefaults.standardUserDefaults()
let i = 0
ud.setInteger(i, forKey: "Score")
ud.setObject(i, forKey: "Score")
let n = 0 as NSNumber //顯式類型轉換
let u = NSNumber(float:0) //直接使用NSNumber構造方法
let dec1 = NSDecimalNumber(float: 4.0)
let dec2 = NSDecimalNumber(float: 5.0)
let sum = dec1.decimalNumberByAdding(dec2)
4.5 NSValue
4.6 NSData
let ud = NSUserDefaults.standardUserDefaults()
let c = UIColor.blueColor()
let cdata = NSKeyedArchiver.archivedDataWithRootObject(c)
ud.setObject(cdata, forKey: "myColor")
archivedDataWithRootObject:
方法, 最後將這個cdata保存到ud中.4.7 判等和比較
infix operator + {
//....
}
isEqual
對象方法, 該方法從NSObject中繼承.isEqual
方法的調用.
let n1 = NSNumber(integer: 1)
let n2 = NSNumber(integer: 2)
let n3 = NSNumber(integer: 3)
let ok = n2 == 2
let ok2 = n2 == NSNumber(integer: 2)
let ix = [n1, n2, n3].indexOf(2)
class Dog : NSObject {
var name : String
init(name:String) {
self.name = name
}
}
let d1 = Dog(name: "Fido")
let d2 = Dog(name: "Fido")
let ok = d1 == d2
isEqualToNumber:
等等.但這些類上面同樣實現了isEqual, 在Swift中肯定是使用”==”比使用isEqualToNumber
方法來得更加簡便.compare:
, 返回NSComparisonResult對象, 它有三種結果:.OrderedAscending
升序.OrderedSame
相等.OrderedDecending
降序
let n1 = NSNumber(integer: 1)
let n2 = NSNumber(integer: 2)
let ok = n1 > n2 //錯
let n1 = NSNumber(integer: 1)
let n2 = NSNumber(integer: 2)
let ok = n1.compare(n2) == .OrderedAscending //true
.OrderedAscending
,故ok值為true.4.8 NSIndexSet
let arr = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 11]
let ixs = NSMutableIndexSet()
ixs.addIndexesInRange(NSRange(1...4))
ixs.addIndexesInRange(NSRange(8...9))
let arr2 = (arr as NSArray).objectsAtIndexes(ixs) //arr2 為[8, 7, 6, 5, 1, 0]
for...in
遍歷NSIndexSet中存放的下標,也可以使用enumerateIndexesUsingBlock:
或enumerateRangesUsingBlock:
以及它們的變體來遍歷.4.9 NSArray and NSMutableArray
objectAtIndex
來獲取它裡面的元素.下標從0開始,故最後一個元素下標為count - 1.objectAtIndex
,也可以直接使用下標操作符,即用諸如”[0]”獲取指定下標的元素.objectAtIndexedSubscript:
方法.indexOfObject:
或indexOfObjectIdenticalTo:
方法來查找元素.前者調用該類中定義的isEqual, 而後者使用的是類似Swift中”===”的方式. 前面也說過,如果沒有找到元素,則返回的是NSNotFound.
let marr = NSMutableArray()
marr.addObject(1) //數值先自動轉存成了NSNumber以存放到可變數組中
marr.addObject(2)
let arr = marr as NSArray as! [Int] //先轉換成NSArray,再轉換成對應類型的Swift數組.
let pep = ["marry", "joe", "mood"] as NSArray
let ems = pep.objectsAtIndexes(pep.indexesOfObjectsPassingTest({ (obj, idx, stop) -> Bool in
return (obj as! NSString).rangeOfString("m", options: .CaseInsensitiveSearch).location == 0
}))
let s = ems as! [String]
print(s) //["marry", "mood"]
4.10 NSDictionary and NSMutableDictionary
init()
或init(dictionary:)
.objectForKey:
方法, 如果key存在, 則返回對應值對象, 若不存在, 則返回nil. 但是在OC中, nil並非對象, 因此不能作為NSDictionary的值對象.objectForKey
的返回值的辦法是將返回值作為一個AnyObject?
, 即一個包裝任意類型的可選值.objectForKeyedSubscript:
方法, 而Swift將該方法等價為下標的getter. 另外在NSMutableDictionary中還實現了setObject: forKeyedSubscript:
方法, Swift將該方法等價為對下標的setter.4.11 NSSet and Friends
isEqual
比較都不會返回true. 無關的就是指兩個對象之間不存在邏輯關系.for...in
遍歷Set, 當然遍歷出來的值是無序的.
setObject: atIndexedSubscript:
方法).4.12 NSNull
NSNull()
獲取該單例.isEqual
方法, 即判斷兩個對象指針是否相等.4.13 Immutable and Mutable
copy
或mutableCopy
生成某個對象的不可變或可變副本. 但這樣的方式沒有任何方便可言, 因為這些副本是AnyObject的,需要進行類型轉換.
4.14 Property List
writeToFile:atomically:
和writeToURL:atomically:
方法可以方便實現Property List的生成和存放, 只需要給出文件的路徑或URL即可. 這兩個類也提供了通過文件生成數組或字典的方法.NSPropertyListSerialization
類型.詳見蘋果Property List Programming Guide.5 Accessors, Properties, and Key-Value Coding
@property
, 使用這個指令聲明的屬性會自動獲得setter和getter, 並且符合命名規范, 比如:
@property(nonatomic) CGRect frame;
frame:
, setter為 setFrame:
.
var frame : CGRect
.frame
語法, 實際是調用該屬性的accessor方法.setFrame
來設置屬性, 但在Swift中是不允許的. 即如果OC類中有@Property聲明的屬性, 該屬性的accessor方法對Swift隱藏的.readonly
,它和Swift中屬性後的{get}類似, 表示該屬性是只讀的. 即當遇到readonly, Swift自動將它看做{get}.5.1 Swift Accessors
.prop
來訪問這個屬性, 讀取或設置它, 即使在Swift中你並沒有實現任何這樣的訪問方法. 其實這個調用被轉接到了隱式訪問方法調用上面去.
var color : UIColor {
get {
print("getter was called.")
return UIColor.redColor()
}
set {
print("setter was called.")
}
}
ViewController* vc = [ViewController new]; //OC方式新建一個ViewController對象
[vc setColor:[UIColor redColor]]; //顯式調用setter, 輸出 setter was called.
UIColor* c = [vc color]; //顯式調用getter, 輸出 getter was called
@objc(hue) var color : UIColor?
hue
, setter是setHue
.
class Myview : UIView {
override var frame : CGRect {
didSet {
print("g")
}
}
}
5.2 Key-Value Coding (KVC)
respondsToSelector
方法的原理類似( selector名稱也是一個字符串).NSKeyValueCoding
協議, 它是一個informal protocol, 為一個category, 這個category被注入到NSObject中.valueForKey:
以及setValue:forKey:
. 當某對象調用二者之一時, 這個對象便先進行自回應, 就是先嘗試有無訪問方法, 有則調用訪問方法, 沒有則直接訪問該key名稱對應的實例變量.dictionaryWithValuesForKeys:
和setValuesForKeysWithDictionary:
, 這兩個方法允許在字典上使用一條語句就訪問(設置或獲取)若干字典內的鍵值對.valueForKey:
時, Swift會接收到一個包裝著[AnyObject]的可選對象, 隨後就可以將它轉換為需要的類型了.
如果嘗試訪問一個不兼容的key, 則出現運行時異常, 比如下面構造一個這樣的異常:
let obj = NSObject()
obj.setValue("hello", forKey: "people") //Crash.
setPeople
或是一個實例變量:people
.
class Dog : NSObject {
var name : String = ""
}
//...使用時:
let d = Dog()
d.setValue("fido", forKey: "name") //完全可以
print(d.name) //輸出fido, 可以看到起到了作用!
5.3 Uses of Key-Value Coding
valueForKey:
, 相當於對數組中每一個元素對象都發送valueForKey:
消息, 並且返回一個新生成的value數組. 這種方法十分簡便實用. 在NSSet中也有類似用法. 在NSDictionary中實現了valueForKey:
, 可以替代objectForKey:
, 這在當你需要操縱一個字典數組的時候尤其有用. 而NSMutableDictionary中的setValue:forKey:
方法等價於setObject:forKey:
, 而且前者設置的value可以是nil, 因為設置成nil時會自動調用removeObject:forKey:
. NSSortDescriptor對數組進行排序的原理是向每一個元素發送valueForKey:
消息. 這樣可以方便進行字典數組的元素排序. NSManagedObject經常和Core Data結合使用. 它是KVC兼容的, 當在實用模式下配置了它的屬性, 就可以通過valueForKey:
以及setValue:forKey:
來訪問. CALayer和CAAnimation允許用戶使用KVC來定義或修改任意的鍵值, 就好像這些鍵值對就是存放在字典中的一樣, 而實際上是因為KVC兼容. 這兩個類對象通過KVC來配置的話是非常方便的. 而實際工作中也的確是經常使用KVC來配置這兩個類的對象.
5.4 KVC and Outlets
setMaster:
, 然後Dog對象的set方法被隱式調用, 並將person對象設置到master上面.5.5 Key Paths
valueForKeyPath:
和setValue:forKeyPath:
方法來應用key鏈進行鏈式訪問."key1.key2.key3"
."key1.key2"
, 在valueForKeyPath:
中使用這個KeyPath, 實際過程是先用key1調用valueForKey:
, 再在返回值上利用key2繼續調用valueForKey:
, 以此類推.
var theData = [
[
"description" : "The one with glasses.",
"name" : "Manny"
],
[
"description" : "Looks a little like Governor Deway.",
"name" : "Moe"
],
]
let arr = myObject.valueForKeyPath("theData.name") as! [String]
5.6 Array Accessor
5.7 The Secret Life of NSObject
Class
類型的對象, 並且這些類都繼承自NSObject(包括Class
). 所有的NSObject實例方法都可以被類對象以類方法的形式調用.比如respondToSelector
方法是在NSObject中定義的對象方法, 但這個方法可以被任意的類對象作為類方法調用.
description
, 以及對象比較isEqual
.doesNotRecognizeSelector
. 詳見Object-C Runtime Programming Guide.performSelector
方法, 它使用selector作為參數, 使對象進行selector對應的行為. 假如當運行時才知道需要發送什麼消息給該對象, 就只有使用這個方法. 另外, performSelector
的變體允許你在特定線程上面發送消息, 或允許在特定時間間隔後發送消息, 比如 performSelector:withObject:afterDelay:
.