Don't wanna be here? Send us removal request.
Text
從Power Mac G5回顧CPU RISC與CISC的戰爭
單核心的PowerPC 970FX。圖源自Wiki。
筆者生平接觸的第一台Mac SE30。裡面就是這顆Motorola的68030 CISC架構CPU。圖源自Wiki。
說起來,筆者接觸Mac電腦不算晚,從最早的Motorola 680X0時代,後來的PowerPC 601/604,還有比較近代一點的PowerPC G3/G4/G5。這個時期CPU的筆戰,總環繞著CISC(複雜指令集)跟RISC(精簡指令集)孰優孰劣的討論。那時候,筆者對於RISC架構的理解(也就是當時Apple所屬PowerPC陣營所採用CPU的架構),因為硬體指令數目少、指令長度對齊、執行時間短等特性,所以有利於電腦程式碼在編譯器編譯上的排程與優化。另一項優勢也是同樣來自於精簡指令集本身的特性,採用RISC架構的CPU能夠透過二級快取容量的加大,相對於CISC而更有效率的受益於快取記憶體增大換得CPU運算能力的提升。
後來的第二台Mac,Power Mac 7100/66AV。就是用上面的這顆PowerPC 601,Apple開始採用RISC架構的CPU。圖源自Wiki。
後來多媒體指令概念的問世。PowerPC跟x86也都各自發展出對應多媒體影音應用的強化架構,分別是PowerPC的AltiVec (Apple稱為Velocity Engine)和Intel x86的MMX。基本上,雖然同樣強調針對多媒體影音世代的來臨所加入CPU的指令集,但是兩者的設計哲學仍然有所不同。AltiVec強調的是浮點"運算"的本身運算加強,因此可視為浮點運算的擴充或延伸。而MMX是針對特定反覆高頻率多媒體的運算應用優化,甚至該稱為特化。因為MMX強調的多媒體運算能力,是在CPU既有的整數/浮點運算能力之外,增設一種不同於前面兩者,但能透過程式撰寫編譯後,直接提高CPU對於多媒體資料的處理能力。
Power Mac G4 MDD上的PowerPC 7455。自G4處理器之後,開始加入AltiVec運算單元至CPU中。此時PowerPC處理器的時脈已經過了GHz大關,二級快取配置最大有到2MB之多。
然而,看似兩個CPU陣營為各自的處理器設計哲學無不卯足了全力時。AMD公司對於CPU市場的參與,可以說是讓本來已經精采非常的處理器戰局,產生了更加奇妙的漣漪。AMD的處理器設計,用較簡單的理解方式或許可以這樣來看,極盡可能的採用RISC上的效能強化理論來達到x86指令相容的目的。因此AMD處理器的暫存器使用非常具有彈性。甚至從後來3DNow!的導入,AMD處理器在遊戲上的浮點運算能力能夠超越當時同期Intel時脈相當的CPU。由此可以看出AMD當時在CPU設計上的成功,有效透過處理器架構的優勢來縮短本身與Intel工業製程上時脈的差距。高頻穩定運作的CPU需要優異的工業製程還有材料科技來生產。所以有相當長的一段時期,消費者是以處理器運作的時脈來鑒別處理器運算的能力。即使今日,這樣的印象仍然根深柢固地深植在使用者的心中。
以管線超深著名的Pentium 4 Prescott。Intel這個時期推出的高端Pentium 4隨便一顆都落在3GHz~4GHz之間。圖源自Wiki。
事實上,PowerPC G5 970FX/970MP還有Intel Pentium 4 Northwood/Prescott都在高時脈高熱的問題上嘗到了苦頭。為了因應G5的高熱,Apple在每一代Power Mac G5的頂級款配置上,皆採用了LCS(Liquid Cooling System)作為冷卻CPU的手段。沒想到卻因此為日後冷卻液外滲,導致電源供應器/主機板短路燒毀而種下設計不良的惡名。即使最末代的四核心(兩顆雙核心配置的G5 970MP)冷卻液外滲的情形已經大為減少(跟前代June 2004 Dual G5相比可以說是了了無幾),但使用者已經對於花大錢購置配有液冷系統的Mac信心大失。至於Pentium 4也由於時脈的提昇與SSE/SSE2/SSE3指令集的導入,卻無法比起前代自家處理器有明顯的效能提昇,而空放江山讓AMD有著大鳴大放的機會。
AMD Althon 64採用HyperTransport解決前端匯流排的傳輸瓶頸。圖源自Wiki。
而隨著CPU運算能力的長足發展,另一項效能瓶頸的問題逐漸浮現-前端匯流排。前端匯流排扮演的作用,相當於餵資料給CPU運算的入口。因此可以想見提供資料給CPU運算的能力必須要能跟上CPU消化資料的速度。以PowerPC 970MP來說,Power Mac G5的設計是以雙向高時脈的前端匯流排在運作。Apple在自己的技術資料上註明這是一個採用雙向、相對於CPU時脈二比一64位元的前端匯流排。以筆者2.3GHz Power Mac G5而言,前端匯流排是以1.15GHz的時脈來運作。而在相當於同期的Pentium 4 Prescott,其前端匯流排則是較低的800MHz或533MHz。類似於處理器時脈並非效能的一切,AMD也在同樣傳輸瓶頸的問題上攻下一城,提出了HyperTransport架構作為解決方案。這是一種可序列可並列、高頻率、雙向、低延遲的匯流排技術。相對於原先並列傳輸的匯流排技術,HyperTransport以序列傳輸簡化前端匯流排的複雜性,輕易地提供比之前的技術高上許多的頻寬。而之後更直接地把記憶體控制器內建到CPU中,將記憶體與處理器間的延遲盡可能的降到最低。
Turbo Mode(CPU動態超頻技術),PowerGate(閒置核心關閉的節能技術),內建DDR3記憶體控制器,捲土重來的Hyperthreading技術,序列傳輸的Quick Path Interconnect,可以說是集Intel近年技術大成的i7 CPU。圖源自Wiki。
當然好的概念與技術很快的就會被競爭的對手採用到自家的產品內。AMD 3DNow!獲得成功後的不久,Intel很快的也發表了自家的SIMD(Single Instruction Multiple Data)指令架構SSE(Streaming SIMD Extensions)。在概念上,SSE算是有點向AltiVec和3DNow!的設計哲學靠攏。表面上SSE一開始似乎沒有如3DNow!獲得如雷貫耳的掌聲。但是由於先天x86 CISC的包袱加開外掛導入RISC架構的作法,Intel自始自終其實深知編譯器針對處理器最佳化對於軟體表現效能上的重要,所以從SSE一代接著一代到如今最新的SSE4.2,Intel花了相當長的時間耕耘編譯器的領域,讓程式開發者熟悉接受甚至深黯SSE指令的運作。強者如Maxxuss甚至直接以SSE2指令,以組合語言直接實作出SSE3指令集的模擬器(可以想見有為數眾多高手在這上面鑽研至深)。另外在影音轉檔上也有為數眾多的高手極盡所能地將程式碼針對SSE改寫,因此在影音轉檔的應用上,筆者自己的經驗還是覺得Intel架構的CPU仍然是目前市場上相關應用的首選。以編譯器釜底深耕的策略延伸至應用軟體在單機桌上電腦的表現,事實上相當地有效解決CISC所承襲的包袱和效能瓶頸。而在前端匯流排的部份,Intel也在先前剛發表沒多久的i7 CPU家族上,使用了與HyperTransport類似的傳輸技術-Quick Path Interconnect。而同樣的也在i7 CPU家族內整合了支援三通道DDR3的記憶體控制器。
雙核心PowerPC 970MP的內部Die shot,左右上方是各自1MB的二級快取記憶體。圖源自InfoMars論壇。
說了這麼多編譯器針對CPU指令集最佳化所帶來的效能提昇。那在沒有最佳化的情況呢?或是換個角度來想,雖然多媒體指令的強化可以大幅改善相關應用的效能,但是並非每個使用者都高度依賴這類的程式,甚至我們每天使用電腦的情形,可能有泰半也都非多媒體方面的應用。在處理器時脈尚未進入GHz大關之前,直接CPU工作時脈的提昇所換來的效能增長是非常直接而且容易理解。然而當時脈進展到2GHz之後,一方面是工業技術製造上的困難,還有處理器本身材質的物理條件限制(像是高頻同時帶來的高熱與耗電),時脈增加所換來的性能增益逐漸平緩,甚至造成冷卻系統設計上的困難(如Power Mac G5液冷系統血淋淋的例子)。因此既然火力無法繼續增強,改採人海戰術就是可行的另一項作法。也就是在時脈沒有增加的情形下,在同一顆CPU內放入多個處理器核心。雖然說起來很容易,但是在實行上有很大的問題需要克服。最直接面臨的問題就是指令的排程和快取資料的一致才能確保運算結果的正確性。說起來似乎又回到了處理器指令管線化(Instruction Pipeline),超純量執行(Superscalar),分支預測(Brach Prediction)的老路。確實透過實質上應用程式撰寫手法的改變,還有搭配編譯器針對多核心CPU最佳化才能真正百分百發揮多核心處理器的好處。然而實際上即使拿既有針對單核心單CPU撰寫的軟體在雙核單CPU上運作,也多少能獲得20-30%不等的效能增進。就如G5 970MP的問世時,Apple同樣也聲稱單CPU雙核的2.3GHz G5比上一代單核雙CPU的2.5GHz G5有著更好的效能。或許,這裡面有一部分要歸功於作業系統運作方式不約而同地契合於單CPU多核心的處理器架構。如今作業系統本身緒(Thread)的執行,就在某種程度上可以視作一種軟體的即時排程,一個應用程式對應一個緒在執行,應用程式彼此透過作業系統記憶體保護的規範來確保彼此運作互不干擾。換個角度來看,作業系統扮演一個將硬體抽象化的角色,前端應用程式對硬體的要求由作業系統接單,而後端的處理器只要負責應付由作業系統丟過來的執行緒,在硬體上盡可能的將所有的快取,暫存器和運算核心派上用場,然後把作業系統送過來的執行緒一一消化掉。簡單來說儘管處理器變成多核心,但整體實質來看仍然是單一的一顆CPU,因此在作業系統核心沒有全面改寫的情況下,僅僅只是作業系統層級排程的優化,也可以因為多核心的處理器帶來立即效能上的好處。雖然不如編譯器針對個別如整數或浮點運算一一排程優化所帶來的效能提昇明顯,但相對於原先單核心多處理器的硬體架構來說,單CPU多核心已經是很大的一個突破。不像更早前如Power Mac G4 MDD,雖然具備配置雙處理器的架構,但第二顆CPU泰半的時候往往是英英美代子地閒置中。所以說筆者自己喜歡這樣去理解(其實不確定正不正確),多核心的實質進步就是從處理器內部的指令管線化,擴大進展到作業系統執行緒的類管線化。 談到這裡或許有些朋友會想到Intel多核心的前身,Pentium 4上出現過如今又在i7 CPU Family上捲土重來的超執行緒(Hyperthreading)到底與雙核心有什麼不同。簡單來講Hypethreading是一種邏輯上的雙核心,其目的是在既有的處理器管線中盡可能的把CPU指令給塞滿,而且超執行緒的運作至少需要作業系統核心的配合。然而因為實體核心並沒有實質增加,因此當發生資源互搶的情形時,超執行緒反而有可能造成效能低落。以筆者自己的經驗,超執行緒對於影音轉檔這種大量重複且固定的演算式運算帶來的效能好處最為直接明顯。
早期Pentium 4開始在高階使用的CPU加入Hyperthreading的功能。雖然邏輯上屬雙核心,實體依然是單核心單CPU。圖源自Wiki。
拉拉雜雜的扯了這麼多,大家可能會有個疑問。到底那一系列的CPU才是效能的王道,Apple捨PowerPC家族改採Intel x86是正確的抉擇嘛?尤其Pentium 4的NetBurst架構曾經得來Engineering Failure的評價。事實上筆者想說的是,若談到效能時筆者從未覺得有過那顆處理器終極地主宰著過去數十年桌上"個人"電腦CPU效能霸主的地位。套句蕪園樓主香讀秀的名言:『虛名~虛名~浮雲而已』。由於桌上個人電腦本身使用者特性使然,對於非高端動畫或科學運算依賴的使用者而言,CPU帶來的效能長進往往使用者無法輕易的察覺。甚至對於多數使用者來說,換上SSD固態硬碟,或是遊戲玩家直接更換3D顯示卡所帶來的效能體驗可能還比較直接有感。因為繁複多元的數位應用,使得並非單單處理器的效能就能片面決定個人電腦的性能價值。另外處理器、應用程式還有作業系統彼此契合無間的合作,也是同樣缺一不可。雖然Apple捨PowerPC家族改採Intel x86傷了一些老玩家的心,而在之後問世的Snow Leopard Mac OS X 10.6支援上,捨棄PowerPC支援再一次傷了許多G5 Users的情感(這也讓筆者有低價入��Power Mac G5的機會)。但是效能極有可能不是Steve Job腦袋裡面所轉的最大考量。Apple賣的是科技產品的新奇體驗,而非高科技硬體本身。Mac OS X已經證明了可以輕易的移植到不同的CPU家族,像是iPhone/iPod/iPad所採用ARM架構的CPU。透過作業系統核心的替換,AMD家族的CPU也同樣可以順暢地執行Mac OS X,甚至目前Snow Leopard的微核心,大家也可以透過lipo -info指令,來看出10.6的核心mach_kernel仍然是與PowerPC以Fat Binary的形式相容的。 所以最大的考量在哪裡?筆者自己的揣測是多元而價格充滿彈性的處理器供應鏈。原先PowerPC家族是由IBM所獨家提供,加上IBM本身針對的是企業用戶市場,因此處理器生產上的配合度到了2004~2005時,似乎漸漸的無法快速回應滿足Apple的期待與要求。而在x86家族相容的處理器市場則有很大的不同,AMD處理器如芒在刺地牽制著Intel,因此讓處理器供應價格與量的需求上,在在地讓Apple有更多的談判空間。從另外一方面來看,Apple對於零組件的供應在態度上有相當程度的自主強烈意識。像是入主LG面板的投資,或是跨足半導體IC設計,如iPad的A4晶片。甚至之前市場曾謠傳過Apple想以八十億美元買下ARM公司。因此IBM遲遲無法推出G5 CPU的行動版本,很可能真的是壓垮導致Apple與PowerPC家族分道揚標的最後一根稻草。 我們若直接來看2005年應用在Apple桌上電腦的PowerPC 970MP處理器。雙核心,虛擬化技術,LPAR Micro-Partitioning(一種源自於Power5家族,屬於處理器層級的硬體資源邏輯分割技術)。可說與今日的處理器設計相比並無到遙遙落後的地步,而實際上IBM處理器的Power家族也持續就Power6、Power7一代跟著一代進一步地往伺服器領域的高效能平行運算發展中。在筆者自己而言,筆者很喜歡970MP在於個人桌上電腦曾經所扮演的獨特性,也同時欣喜Mac OS X轉進x86 CPU家族的市場策略成功。Intel和AMD的較勁讓使用者對於未來的電腦科技有更多的想像。而撰寫此文僅以回首電腦處理器發展史上,曾經有過一個採用PowerPC家族的桌上電腦世代。
0 notes
Text
Find and return the ranges of all the occurrences of a given string in Swift
Return the first occurrence multiple times to collect all occurrences
If you look up Apple Developer Documentation, you will find the description of function range(of:options:range:locale:) which is identical to the quote below.
Finds and returns the range of the first occurrence of a given string within a given range of the String, subject to given options, using the specified locale, if any.
Yes, it only returns the first occurrence. Therefore, to fulfill our purpose, we need to construct a while-loop to iterate all the occurrences of a given string within the receiver. A reference snippet will be like the following code block. And note that an offset is also added to reduce the length of range in each iteration. So actually, the position of each loop is basically based on the upper bound of the previous iteration.
extension String { func indices(of occurrence: String) -> [Int] { var indices = [Int]() var position = startIndex while let range = range(of: occurrence, range: position..<endIndex) { let i = distance(from: startIndex, to: range.lowerBound) indices.append(i) let offset = occurrence.distance(from: occurrence.startIndex, to: occurrence.endIndex) - 1 guard let after = index(range.lowerBound, offsetBy: offset, limitedBy: endIndex) else { break } position = index(after: after) } return indices } }
Transform indices to ranges
In applying above code to a real case, sometimes returning all indices is not enough to do find-and-replace-all operations in text editing. Therefore, a transform helper can help to generate a range array that is much easier to deal with substrings.
extension String { func ranges(of searchString: String) -> [Range<String.Index>] { let _indices = indices(of: searchString) let count = searchString.characters.count return _indices.map({ index(startIndex, offsetBy: $0)..<index(startIndex, offsetBy: $0+count) }) } }
Create NSRange from Range before Swift 4
If you have shifted your Xcode to version 9. The Swift standard library of Swift 4 provides a method to convert from Range<String.Index> to NSRange directly. It’s definitely a feature that many developers are looking forward to it because it’s still unavoidable to deal with NSRange sometimes when certain function calls are not fully migrated to native Swift yet, like an example of NSAttributedString below.
let str = "Hello, playground! Hello, world! Hello, earth!" let attributedString = NSAttributedString(string: str) attributedString.enumerateAttributes(in: NSRange(location: 0, length: str.utf16.count), options: NSAttributedString.EnumerationOptions.longestEffectiveRangeNotRequired) { (attributes, range, stop) in }
In order to have a smoother migration from the current version of Swift to Swift 4. We can implement a grammar compatible function to create NSRange from Range before Xcode 9 is released officially.
extension NSRange { init(_ range: Range<String.Index>, in string: String) { self.init() let startIndex = range.lowerBound.samePosition(in: string.utf16) let endIndex = range.upperBound.samePosition(in: string.utf16) self.location = string.distance(from: string.startIndex, to: range.lowerBound) self.length = startIndex.distance(to: endIndex) } }
Test above functions in the playground
Basically, all above codes are necessary elements we need to find and return the ranges of all the occurrences of a given string. Last, let's test and see a result by the snippet below.
let str = "Hello, playground, Hello, playground, Hello, playground, Hello, playground, Hello, playground" let indices = str.indices(of: "round") print("[Int] : \(indices)") let ranges = str.ranges(of: "playground") for range in ranges { print("`Range` : " + str.substring(with: range)) } for range in ranges.map({ NSRange($0, in: str) }) { print("`NSRange` : " + (str as NSString).substring(with: range)) }
And the log console will be seen like this in the playground.
[Int] : [12, 31, 50, 69, 88] `Range` : playground `Range` : playground `Range` : playground `Range` : playground `Range` : playground `NSRange` : playground `NSRange` : playground `NSRange` : playground `NSRange` : playground `NSRange` : playground
0 notes
Text
Higher Order Functions in Objective-C
No, you did not get fooled by your eyes. The title is not a typo.
First, let's take a look at how it performs in Swift playground
If you pay attention to the snippet below, you will notice the intent of codes itself is focus on function for each element in a collection and there are no signs of a for-loop pattern. It helps developers to focus on more operations itself but not control flow with data process.
// Map: Iterate a collection and applies the same block operation to each element in it. print(characters.map({ String($0).uppercased() })) // Filter: Iterate a collection and return elements that meet a condition. print(characters.filter({ $0 == "o"})) // Reduce: Combine all elements in a collection to create a single output. print(characters.reduce("", { String($0) + String($1) })) // FlatMap: Flatten a collection of collections. let _characters = ["Hello".characters, ", ".characters, "playground".characters] print(_characters.flatMap({ $0 }))
Okay, now let's get hands dirty
Unlike Swift, higher order functions are not built-in natively in Objective-C. Therefore, we need to create our own category to have these functions be accessible. If you browse the codes below, the core concept is not complicated basically. It's all around the function enumerateObjectsUsingBlock: and adds some minor variation according to its purpose.
- (NSArray *)map:(id (^)(id obj))block { NSMutableArray *mutableArray = [NSMutableArray new]; [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { [mutableArray addObject:block(obj)]; }]; return mutableArray; } - (NSArray *)filter:(BOOL (^)(id obj))block { NSMutableArray *mutableArray = [NSMutableArray new]; [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if (block(obj) == YES) { [mutableArray addObject:obj]; } }]; return mutableArray; } - (id)reduce:(id)initial block:(id (^)(id obj1, id obj2))block { __block id obj = initial; [self enumerateObjectsUsingBlock:^(id _obj, NSUInteger idx, BOOL *stop) { obj = block(obj, _obj); }]; return obj; } - (NSArray *)flatMap:(id (^)(id obj))block { NSMutableArray *mutableArray = [NSMutableArray new]; [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { id _obj = block(obj); if ([_obj isKindOfClass:[NSArray class]]) { NSArray *_array = [_obj flatMap:block]; [mutableArray addObjectsFromArray:_array]; return; } [mutableArray addObject:_obj]; }]; return mutableArray; }
So, how to use them?
The usage is very similar to the ways in Swift but little wordy indeed :) However, the patterns and ideas are still the same. We only take care operations and no need to deal with flow controls.
NSArray *array = @[ @"H", @"e", @"l", @"l", @"o", @",", @" ", @"w", @"o", @"r", @"l", @"d", @"!" ]; // Map: Iterate an array and applies the same block operation to each element in it. NSLog(@"%@", [array map:^id(id obj) { return [(NSString *)obj uppercaseString]; }]); // Filter: Iterate an array and return elements that meet a condition. NSLog(@"%@", [array filter:^BOOL(id obj) { return [(NSString *)obj isEqualToString:@"o"]; }]); // Reduce: Combine all elements in an array to create a single output. NSLog(@"%@", [array reduce:@"Hey, " block:^id(id obj1, id obj2) { return [NSString stringWithFormat:@"%@%@", obj1, obj2]; }]); array = @[ @[@"H", @"e", @"l", @"l", @"o"], @[@",", @" "], @[@"w", @"o", @"r", @"l", @"d", @"!"] ]; // FlatMap: Flatten an array of arrays. NSLog(@"%@", [array flatMap:^id(id obj) { return obj; }]);
But.... Wait! there might be something wrong here!!
Yes, the devil is always in the details. That's why BUT is so important all the time. As a developer with years experience, you might wonder if is it a good practice in Objective-C? The reason is that type safety becomes developers' responsibility more or less. After all, Swift and Objective-C don't share the same philosophy at type inference and type safety in many ways. My suggestion is to add a class restrictor for each function to improve safety. Although adding class restrictor might become wordier in writing, I still think the safer the better in the long run. The snippet below is to demonstrate the usage with a class restrictor. The full implementation is available at GitHub here.
NSArray *array = @[ @"H", @"e", @"l", @"l", @"o", @",", @" ", @"w", @3, @"o", @"r", @"l", @"d", @"!" ]; // Map: Iterate an array and applies the same block operation to each element in it. NSLog(@"%@", [array map:^id(id obj) { return [(NSString *)obj uppercaseString]; } class:[NSString class]]); // Filter: Iterate an array and return elements that meet a condition. NSLog(@"%@", [array filter:^BOOL(id obj) { return [(NSString *)obj isEqualToString:@"o"]; } class:[NSString class]]); // Reduce: Combine all elements in an array to create a single output. NSLog(@"%@", [array reduce:@"Hey, " block:^id(id obj1, id obj2) { return [NSString stringWithFormat:@"%@%@", obj1, obj2]; } class:[NSString class]]); array = @[ @[@"H", @"e", @"l", @"l", @"o"], @[@",", @" ", @3], @[@6, @8, @6], @[@"w", @"o", @"r", @"l", @"d", @"!"] ]; // FlatMap: Flatten an array of arrays. NSLog(@"%@", [array flatMap:^id(id obj) { return obj; } class:[NSString class]]);
0 notes
Text
Write an extension function for abstract type aggregates [2nd]
Swift itself is a statically typed language
Generic code enables functions and types that can work with different types
In general, a function is usually a piece of codes to be reusable and used to perform a single related action; therefore, an ordinary form of a function is common as follows:
func function_name(parameters: declared_type) -> return_type { //... Code here return typed_constants }
Traditionally, we could create functions for the same purpose individually to work with different types. However, it means that we produce codes based on similar patterns for each unique type declaration. And it somehow violates coding in a clean way and maintenance-free which is always a goal and principle to programmers. In view of that, we want a manner which is abstracted and able to expresses its intention in a clear form. And that's what we called Generic.
Let's start from a generic entity that can be reused with different types
A generic entity must have a type parameter. The "T" below stands for whatever type will eventually be used in the generic entity.
struct GenericStruct<T> { var someThing: T } // After above manner, GenericStruct can be constructed and parameterized over different types by adding type parameters in a pair of angle-brackets. let a = GenericStruct<Int>(someThing: 1) let b = GenericStruct<Float>(someThing: 2.5) let c = GenericStruct<String>(someThing: "Hello, world!")
Then, what if we put a generic entity in a collection?
It looks like that defining a generic entity is easy and straightforward. However, the story will not that simple always. If we do want to put our generic entity in a collection type, like Set, we need to have some more decoration and adopt Hashable protocol for that purpose. The snippet below is a revised version.
struct GenericStruct<T: Hashable>: Hashable { var someThing: T var hashValue: Int { get { // Hashable in angle-brackets is a constraint to tell compiler that type T must have .hashValue return someThing.hashValue } } static func ==(lhs: GenericStruct, rhs: GenericStruct) -> Bool { return lhs.hashValue == rhs.hashValue } } let set: Set = [ GenericStruct<String>(someThing: "Watermelon"), GenericStruct<String>(someThing: "Orange"), GenericStruct<String>(someThing: "Banana") ]
Extension function is the last thing that can't be missed
Back to our topic, the purpose is to write an extension function for abstract type aggregates. There is no doubt that the story is not finished yet. The last piece of having an extension function is still missing. We need a third revision of the snippet to have our extension function work with GenericStruct.
extension Set { func getFirstWith<T: Hashable>(value: T) -> Element? { return filter({$0.hashValue == value.hashValue}).first } } let set: Set = [ GenericStruct<String>(someThing: "Watermelon"), GenericStruct<String>(someThing: "Orange"), GenericStruct<String>(someThing: "Banana") ] let output = set.getFirstWith(value: "Orange") print(output!)
Certainly, another type like Int works too. Cheers!
let set: Set = [ GenericStruct<Int>(someThing: 226), GenericStruct<Int>(someThing: 603), GenericStruct<Int>(someThing: 818) ] let output = set.getFirstWith(value: 818) print(output!)
0 notes
Text
String or NSString, which one ought to be
Use native types whenever possible
In general, you should use the Swift native types as possible as you can because they are optimized to compile for better performance. But wait, I just said unavoidable, right? Let's take a look at an Objective-C based class NSRegularExpression as an example.
let str = "Hello, playground" do { let expression = try NSRegularExpression(pattern: "\\s{1,}", options: [.anchorsMatchLines]) let range = NSRange(location: 0, length: str.characters.count) let matches = expression.matches(in: str, options: [], range: range) for match in matches { print(match) } } catch { print(error.localizedDescription) }
In above snippet, we can see the function func matches(in string: String, options: NSRegularExpression.MatchingOptions = [], range: NSRange) -> [NSTextCheckingResult] which only accepts NSRange variable as a parameter; therefore, translating Range type str.startIndex..<str.endIndex to NSRange class will be undoubtedly necessary once we have our codes interoperate and be mixed up String, NSString, Range and NSRange. However, an experienced Swift programmer might find there is a lethal flaw in above snippet also. It's about the way of calculating string length, str.characters.count. The practice like this is highly error prone because Swift uses Extended Grapheme Clusters. Ex, a Unicode smiley symbol. Swift sees it as one character, but the NSString method sees it as two.
So... is there a principal or suggestion?
If we don't want to pay too much attention to the difference like above example, for risk-free, my suggestion is always to cast String to NSString once facing Objective-C based function, like NSRegularExpression. Of course, the strategy will not be necessary until native Swift function is introduced, ex. we might see RegularExpression someday. So, in order to fix above flaw. The way to calculate length should be like the snippet below. All in all, there are always native ways to deal with variables; however, a risk-free or less error prone strategy can save us a day sometimes :)
// Option 1: Cast String to NSString let range = NSRange(location: 0, length: (str as NSString).length) // Option 2: Calculate utf16 count when accessing on a Swift String value. let range = NSRange(location: 0, length: str.utf16.count)
0 notes
Text
Convert NSRange from Range before Swift 4
let str = "Hello, playground! Hello, world! Hello, earth!" let attributedString = NSAttributedString(string: str) attributedString.enumerateAttributes(in: NSRange(location: 0, length: str.characters.count), options: NSAttributedString.EnumerationOptions.longestEffectiveRangeNotRequired) { (attributes, range, stop) in }
In order to have a smoother migration from now to Swift 4. Let's create a grammar compatible function to convert NSRange from Range before Xcode 9 is released officially.
extension NSRange { init(_ range: Range<String.Index>, in string: String) { self.init() let startIndex = range.lowerBound.samePosition(in: string.utf16) let endIndex = range.upperBound.samePosition(in: string.utf16) self.location = string.distance(from: string.startIndex, to: range.lowerBound) self.length = startIndex.distance(to: endIndex) } }
And now, we are able to do conversion like this NSRange(range, in: "A string"). Let's do an experiment. The following processes are trying to convert range0: Range -> range1: Range -> range2: NSRange.
let str = "Hello, playground! Hello, world! Hello, earth!" let range0 = str.range(of: "playground") print(String(describing: type(of: range0))) let range1 = NSRange(range0!, in: str) print(String(describing: type(of: range1))) let start = range1.location let end = start + range1.length let range2 = Range(uncheckedBounds: (start, end)) print(String(describing: type(of: range2)))
0 notes
Text
A tip to extract substring intuitively for Swift
A tip to extract substring intuitively for Swift
With Objective-C, the function func substring(from: Int) -> String is very easy to be implemented because of Int parameter is very intuitive to be taken by human natural. However, the story is very different for Swift side because default inferred string type is String and it only takes String.Index as its parameter.
You might think it's not a problem at all since it's still very easy to bridge a string from class String to class NSString. However, I'm more concerned about what the native way would be for Swift!?
Let's take a look of a real example here:
NSString
let str = "Hello, playground! Hello, world! Hello, earth!" print((str as NSString).substring(from: 0))
String
let str = "Hello, playground! Hello, world! Hello, earth!" print(str.substring(from: str.startIndex))
The difference might not be so obvious in above codes. But what about doing substring(with range: NSRange) -> String instead?
NSString
let str = "Hello, playground! Hello, world! Hello, earth!" print((str as NSString).substring(with: NSRange(location: 0, length: 5)))
String
let str = "Hello, playground! Hello, world! Hello, earth!" let start = str.index(str.startIndex, offsetBy: 0, limitedBy: str.endIndex)! let end = str.index(str.startIndex, offsetBy: 5, limitedBy: str.endIndex)! print(str[start..
Be honest, I really don't want to type so many words all the time since I just want to extract a certain part of a sentence. Actually we can create an extension below to save our day :)
extension String { func substring(from: Int=0, to: Int=Int.max) -> String { guard let start = self.index(self.startIndex, offsetBy: max(0, from), limitedBy: self.endIndex), let end = self.index(self.startIndex, offsetBy: min(to, self.characters.count), limitedBy: self.endIndex) else { return "" } return self[start..
Also noted that parameter from or to can be ignored.
print(str.substring(from: 5)) print(str.substring(to: 5)) print(str.substring(from: 7, to: 17))
0 notes
Text
Write an extension function for abstract type aggregates
Swift itself, is a statically typed language
For statically typed languages, the compiler must have all information about all classes and functions at compile time. However, having functions or methods that can act on different types is still possible. For that purpose, Swift provides Generic.
Let's take a look of an example of an abstract typed struct below. The struct is able to be parameterized over different types by adding type parameters in a pair of angle-brackets. Ex. <T: NameProtocol>.
protocol NameProtocol { var name: String {get set} init(name: String) } protocol ContainStringProtocol: Hashable { var string: String { get } } struct GenericStruct<T: NameProtocol>: ContainStringProtocol { var string: String var hashValue: Int { get { return string.hashValue } } var base: T { return T(name: string) } init(string: String) { self.string = string } static func ==(lhs: GenericStruct, rhs: GenericStruct) -> Bool { return lhs.hashValue == rhs.hashValue } }
Write an extension function for GenericStruct
The protocol ContainStringProtocol is a constraint to have $0.string be taken into account.
extension Set where Element: ContainStringProtocol { func getFirstWith(string: String) -> Element? { return filter({$0.string == string}).first } }
Execute an example to test above extension function
To init GenericStruct, a concrete aggregate such as BaseStruct is necessary to compile an example like the following snippets.
struct BaseStruct: NameProtocol { var name: String } // Example let set: Set = [ GenericStruct<BaseStruct>(string: "Watermelon"), GenericStruct<BaseStruct>(string: "Orange"), GenericStruct<BaseStruct>(string: "Banana") ] let output = set.getFirstWith(string: "Orange") print(output!)
Check log console
GenericStruct<BaseStruct>(string: "Orange") will be seen in the log console.
0 notes
Text
Extending a collection where the element is a custom defined type
Arrays, sets, and dictionaries are three primary collection types in Swift. If we would like to extend a collection where the element is a custom defined type. Generally, a type which is able to conform a certain protocol is necessary.
First, have a protocol be hashable For example, have a protocol be hashable first since the custom type will be stored as an element of a collection.
protocol CustomProtocol: Hashable { var string: String { get } }
Declare a custom defined type Here, let's declare a sturct as an example. Please also noted that other custom defined classes or enums are able to have similiar implementation.
struct CustomStruct: CustomProtocol { let string: String var hashValue: Int { get { return string.hashValue } } static func ==(lhs: CustomStruct, rhs: CustomStruct) -> Bool { return lhs.string == rhs.string } }
Then, create a function in Set extension with constraints specified by a where clause for CustomProtocol
extension Set where Element: CustomProtocol { func getFirstElement(equalTo string: String) -> Element? { return filter({$0.string == string}).first } }
Finally, let's do a test and see its result
// Example let set: Set = [ CustomStruct(string: "Watermelon"), CustomStruct(string: "Orange"), CustomStruct(string: "Banana") ] let output = set.getFirstElement(equalTo: "Orange") print(output)
In my playground with Xcode 8, I can get Optional(CustomStruct(string: "Orange")) in log console.
Create a function in Set extension for CustomStruct only For certain consideration, If create a function in Set extension for CustomStruct only is needed, just change little bit for where clause.
extension Set where Element == CustomStruct { func getFirstElement(equalTo string: String) -> Element? { return filter({$0.string == string}).first } }
0 notes
Text
Implement a type variable to hold a generic parameter
Generic is so powerful in Swift; however, implementing it in a function or even a variable is not so easy and intuitive for new learners.
This time, I would like to demonstrate how to implement a type variable to hold a generic parameter according to what the structure is.
Define a protocol
Let's define a protocol first. We will have our structures later to conform this protocol.
protocol ContentObjectModel: CustomStringConvertible { var text: String { get set } var html: String { get } init(text: String) }
Create a structure to conform ContentObjectModel
The step has nothing special, just create a structure to conform ContentObjectModel.
struct Italic: ContentObjectModel { var text: String var html: String { get { return text } } init(text: String) { self.text = text } var description: String { get { return "\(text.hash)" } } }
Declare a type variable to hold a generic parameter
Finally, the magic is here. Any generic type with constraints specified by protocol ContentObjectModel is able to be hold as a generic parameter like the example below.
struct RegexScanner<T: ContentObjectModel> { var pattern: String var options: NSRegularExpression.Options var type: T.Type { get { return T.self } } init(pattern: String, options: NSRegularExpression.Options=[.anchorsMatchLines]) { self.pattern = pattern self.options = options } }
0 notes
Text
A C function pointer can only be formed from a reference to a ‘func’ or a literal closure
Compare different ways of setting a listener proc for audio object
Note that AudioObjectRemovePropertyListenerBlock doesn’t work for now with Swift
import AudioToolbox // Query an AudioObjectID var inAddress = AudioObjectPropertyAddress( mSelector: kAudioHardwarePropertyDefaultOutputDevice, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster ) var objectID = kAudioObjectUnknown var size = UInt32(sizeof(AudioDeviceID)) AudioObjectGetPropertyData(UInt32(kAudioObjectSystemObject), &inAddress, 0, nil, &size, &objectID) // Query an AudioObjectPropertyAddress inAddress = AudioObjectPropertyAddress( mSelector: kAudioDevicePropertyVolumeScalar, mScope: kAudioObjectPropertyScopeOutput, mElement: kAudioObjectPropertyElementMaster ) var data: Float32 = 0 size = UInt32(sizeof(Float32)) AudioObjectGetPropertyData(objectID, &inAddress, 0, nil, &size, &data) print(data) // Option 1: Closure let listenerProc: AudioObjectPropertyListenerProc = { _, _, _, _ in AudioObjectGetPropertyData(objectID, &inAddress, 0, nil, &size, &data) print(data) return 0 } // Option 2: Func //func listenerProc() -> AudioObjectPropertyListenerProc { // return { _, _, _, _ in // AudioObjectGetPropertyData(objectID, &inAddress, 0, nil, &size, &data) // print(data) // return 0 // } //} // Option 3: [Error] A C function pointer can only be formed from a reference to a ‘func’ or a literal closure //let listenerProc: (AudioObjectID, UInt32, UnsafePointer, UnsafeMutablePointer) -> OSStatus = { _, _, _, _ in // AudioObjectGetPropertyData(objectID, &inAddress, 0, nil, &size, &data) // print(data) // return 0 //} AudioObjectAddPropertyListener(objectID, &inAddress, listenerProc, &data) AudioObjectRemovePropertyListener(objectID, &inAddress, listenerProc, &data)
0 notes
Text
Edit php.ini on Google App Engine
$ sudo /bin/bash # pico /etc/php5/apache2/php.ini # service apache2 restart
0 notes
Text
How to print digits of value in CNLabeledValue
Notice that the property stringValue of CNPhoneNumber are formatted digits.
Further string decoration via higher order function filter() is to remove any symbol containing "-", "(" or ")".
lazy var contacts: [CNContact] = { let store = CNContactStore() var containers = [CNContainer]() do { containers = try store.containersMatchingPredicate(nil) } catch let error as NSError { print(error.localizedDescription) } var contacts = [CNContact]() for container in containers { let predicate = CNContact.predicateForContactsInContainerWithIdentifier(container.identifier) let keysToFetch = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName), CNContactPhoneNumbersKey] do { let results = try store.unifiedContactsMatchingPredicate(predicate, keysToFetch: keysToFetch) for result in results { for phoneNumber in result.phoneNumbers { guard let numberValue = phoneNumber.value as? CNPhoneNumber else { continue } print(numberValue) print(String(numberValue.stringValue.characters.filter({ !["-", "(", ")"].contains($0) }))) } } contacts.appendContentsOf(results) } catch let error as NSError { print(error.localizedDescription) } } return contacts }()
0 notes
Text
Generic Functions as Associated Values in Enums
Practice that the caller can pass in a generic value to be executed by the run function.
Note that enumeration cases can specify associated values of any type to be stored.
enum FormattedResult { case Success(((T) -> Void)) case Failure(((T) -> Void)) func run(a: T) { switch self { case Success(let completion): debugPrint("Success: \(a)") completion(a) case Failure(let completion): debugPrint("Failure: \(a)") completion(a) } } } let f1 = FormattedResult.Success({ a in debugPrint(a) }) f1.run(1) let f2 = FormattedResult.Failure({ error in debugPrint(error) }) f2.run("Error Message Here")
0 notes
Text
Draw a Clock Face with Core Text
Few days ago, I was trying to solve an issue on stackoverflow: Core Text Rotation does not properly rotate - Swift.
Because Core Graphics and Core Text are not things that I usually do all the time, I put the codes below which is revised little bit as a record for review later.
import UIKit class ClockFace: UIView { func drawText(context: CGContextRef, text: String, attributes: [String: AnyObject], origin: CGPoint, angle: CGFloat) -> CGSize { let attributedString = NSAttributedString(string: text, attributes: attributes) let textSize = text.sizeWithAttributes(attributes) let font = attributes[NSFontAttributeName] as! UIFont let rect = CGRect(x: origin.x, y: origin.y + font.descender, width: ceil(textSize.width), height: ceil(textSize.height)) let textPath = CGPathCreateWithRect(rect, nil) let frameSetter = CTFramesetterCreateWithAttributedString(attributedString) let range = CFRange(location: 0, length: attributedString.length) let frame = CTFramesetterCreateFrame(frameSetter, range, textPath, nil) var transform = CGAffineTransformMakeTranslation(origin.x, origin.y + font.descender) transform = CGAffineTransformRotate(transform, angle) CGContextConcatCTM(context, transform); CTFrameDraw(frame, context) return textSize } override func drawRect(rect: CGRect) { let context = UIGraphicsGetCurrentContext()! let radius = rect.size.width/3 CGContextTranslateCTM(context, -radius, rect.size.height + 5) CGContextScaleCTM(context, 1, -1) let tilt = 6 CGContextRotateCTM(context, CGFloat((Double(tilt) / 360) * M_PI)) let attributes = [ NSForegroundColorAttributeName : UIColor.whiteColor(), NSFontAttributeName : UIFont.systemFontOfSize(12)] let origin = CGPoint(x: radius, y: 0) var number: Int = 2 for i in 0..<60 { CGContextSaveGState(context) CGContextTranslateCTM(context, rect.width/2, rect.height/2) CGContextRotateCTM(context, -CGFloat((Double(12 * i) / 360) * M_PI)) if (i % 5 == 0) { number++ number = number > 12 ? number - 12 : number drawText(context, text: "\(number)", attributes: attributes, origin: origin, angle: CGFloat((Double(12 * i - tilt) / 360) * M_PI)) } CGContextRestoreGState(context) } } } let view = ClockFace(frame: CGRectMake(0, 0, 150, 150)) import XCPlayground XCPlaygroundPage.currentPage.needsIndefiniteExecution = true XCPlaygroundPage.currentPage.liveView = view
0 notes
Text
Record audio on a real iOS device
To record audio on a real iOS device, remember to config audio session before a recording is started.
An audio session is the intermediary between an app and iOS used to configure the app’s audio behaviour. If category "AVAudioSessionCategoryPlayAndRecord" is set, it defines iOS audio behaviour to allow audio input (recording) and output (playback).
To record audio in iLBC format, noted to remove AVSampleRateKey key in settings and change file extension to "ilbc" is necessary.
let audioSession = AVAudioSession.sharedInstance() do { try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord) } catch let error as NSError { print(error.description) } let recordSettings = [AVSampleRateKey : NSNumber(float: Float(44100.0)), AVFormatIDKey : NSNumber(int: Int32(kAudioFormatMPEG4AAC)), AVNumberOfChannelsKey : NSNumber(int: 1), AVEncoderAudioQualityKey : NSNumber(int: Int32(AVAudioQuality.Max.rawValue))] do{ try recorder = AVAudioRecorder(URL: getFileURL(), settings: recordSettings) recorder.delegate = self recorder.prepareToRecord() } catch let error as NSError { error.description }
0 notes
Text
Filtering dictionary with zero length strings
Assuming a dictionary that contains some values are empty strings like the following.
["title": "Mr.", "first_name": "Aron", "last_name": "", "city": "Vancouver", "nick_name": "Coconut"]
Practice two higher-order functions forEach() and filter()
Note that if a Dictionary is mutable, filtered key-value paired can be removed directly.
/*: # Filtering Dictionary with zero length strings **Practice two higher-order functions forEach() and filter()** * Assuming a Dictionary that contains some values are empty strings like the following. * ["title": "Mr.", "first_name": "Aron", "last_name": "", "city": "Vancouver", "nick_name": "Coconut"] * Note that if a Dictionary is mutable, filtered key-value paired can be removed directly. */ var dict1: [String : AnyObject] = [ "title" : "Mr.", "first_name" : "Aron", "last_name" : "", "nick_name" : "Coconut", "city" : "Vancouver", ] dict1.forEach { if $1 as? String == "" { dict1.removeValueForKey($0) } } print(dict1) let dict2: [String : AnyObject] = [ "title" : "Mr.", "first_name" : "Aron", "last_name" : "", "nick_name" : "Coconut", "city" : "Vancouver", ] extension Dictionary where Key: StringLiteralConvertible, Value: AnyObject { init(_ elements: [Element]){ self.init() for (key, value) in elements { if value as? String == "" { continue } self[key] = value } } } print(Dictionary(dict2.filter { $1 as? String != "" }))
0 notes