分類彙整: iOS

好書推薦:《人工智慧開發實務 – 使用 Swift》

Practical Artificial Intelligence with Swift 繁體中文版

最近看了《人工智慧開發實務 – 使用 Swift》這本書,覺得這本書寫的真不錯。

不像其他有著艱澀數學與演算法的 Machine Learning 書籍,這本書是屬於實戰類型的書,只用了第十章稍微說明了那些可怕的 ML 演算法,其他章節則教你如何使用 Apple 系統內建的 framework 來做電腦視覺 (Vision)、自然語言處理 (Natural Language)、聲音分析 (Sound Analysis),語音識別 (Speech) 等功能,還有如何使用 CreateML 等工具來訓練自己的 ML 模型。

而且這本書提供了大量的範例程式碼,所以可以很容易的把那些功能運用在現有的 App 上。

這本書真的大推呀!👍 👍 👍

電子專案》使用 iPhone 的 Touch ID 搭配 Arduino Nano 33 解鎖自己的電腦

最近把以前一個用 B4i 寫的 iOS app 用 Swift 重寫,功能是:用 iPhone 的 Touch ID 解鎖自己的電腦。這個程式的原理很簡單,是用一塊 arduino 模擬成 HID Keyboard,然後 iOS app 透過 BLE 把密碼傳送給 arduino 裝置,接著 arduino 裝置再把密碼輸入到電腦,利用這種方式解鎖電腦。

HID Keyboard 的部分,以前是用 Blend Micro 來做,這次則改用 Arduino Nano 33 IoT 來做。

左邊紅色的電路板是 Blend Micro,中間深藍色電路板是 Arduino Nano 33 IoT。

以下是 Demo 影片與 Source code 連結:

詳細情況請看:https://github.com/kyumdbot/AutoPass

Kotlin’s Nullable vs. Swift’s Optional

最近在學 Kotlin 與重學那個學過但沒有學會過的 Swift,發現 Kotlin 的 nullable 與 Swift 的 Optional 非常像,大概有 87 分像吧 (不能再低了)? 所以我做了個對照表:

補充說明:

  1. 使用 ?.as? 運算子時,傳回值的型態一樣會是 nullable (Kotlin) / Optional (Swift),所以對傳回值的後續處理一樣要用 nullable (Kotlin) / Optional (Swift) 的方式對待之。 (心法:只要傳回值有可能出現空值 ( null / nil ),那個傳回值就會是一個 nullable / Optional 。)
  2. Swift Optional 的驚嘆號 ( ! ) 表示直接把 Optional 變數解包並當成有值去對待,所以當變數是空值 (nil) 的時候,對空值調用方法 (或者說:發送訊息) 時當然會當掉,因為空值無法響應那個方法。例如這行程式:str!.uppercased(),當 str 裡是一個 String 的時候,對它調用 .uppercased() 方法是沒問題的,可是當 str 裡是 nil 的時候,這時就會出錯了,因為 nil 沒有 .uppercased() 這個方法,所以會當掉。Kotlin 的 !! 符號是非空斷言 (Not-Null Assertion),告訴編譯器這個地方出現的值不會是 null,所以編譯器會把它當成非空型態去處理,但是,如果這個值不幸是 null 的話,就會拋出一個例外。
  3. Kotlin nullable 與 Swift Optional 不一樣的地方在於:Kotlin nullable 不是一個實際的 Type,例如這行 Kotlin 程式:var str : String? 表示 str 實際上的 Type 會有兩種情況:String 或 null,所以 str 變數的值有可能是一個字串,也有可能是一個空值。但是在 Swift 裡,這行程式:var str : String? 表示 str 的 Type 是一個 Optional,str 變數的值就是一個 Optional,只是這個 Optional 裡的內容可能是一個字串,也可能是一個 nil。也就是説:當一個變數的值可能出現空值時,Swift 用 Optional 去封裝那個變數;但在 Kotlin 裡,把變數宣告成 nullable 的型態時,只是在告訴編譯器,這個變數有可能出現空值 (所以這個變數的 Type 實際上可能會出現兩種情況),然後編譯器就會對這個變數做可空型態的檢查,確保程式碼裡有把出現空值的情況考慮進去。

iOS 筆記》用座標查詢地址

在寫地圖應用的 App 時,有時候需要用到利用座標(經緯度)去查詢那個點的地址,這時可以這麼寫:

#pragma mark - MKMapViewDelegate

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
    // 取得 MapView 中心點座標:
    CLLocationCoordinate2D center = [self.mapView centerCoordinate];
    NSLog(@"緯度:%f, 經度:%f", center.latitude, center.longitude);
    
    CLGeocoder *ceo = [[CLGeocoder alloc] init];
    CLLocation *loc = [[CLLocation alloc] initWithLatitude:center.latitude longitude:center.longitude];
    
    [ceo reverseGeocodeLocation:loc completionHandler:^(NSArray *placemarks, NSError *error) {
        CLPlacemark *placemark = [placemarks objectAtIndex:0];
        if (placemark) {
            // 這個 Dictionary 有地址的相關資訊:
            NSLog(@"addressDictionary %@", placemark.addressDictionary);
                      
            NSLog(@"region: %@",placemark.region);
            NSLog(@"country: %@",placemark.country); 
            NSLog(@"locality: %@",placemark.locality);
            NSLog(@"name: %@", placemark.name);
            NSLog(@"ocean: %@",placemark.ocean);
            NSLog(@"postalCode: %@",placemark.postalCode);
            NSLog(@"subLocality: %@",placemark.subLocality);                 
                      
            NSLog(@"完整地址: %@", placemark.addressDictionary[@"FormattedAddressLines"][0]);
            NSLog(@"City: %@", placemark.addressDictionary[@"City"]);
            NSLog(@"Name: %@", placemark.addressDictionary[@"Name"]);
        } else {
            NSLog(@"Could not locate");
        }
    }];
}

如果是想輸入地址,然後用地址查出座標呢?可以這麼寫:

CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:@"台灣臺北市信義區市府路1號" completionHandler:^(NSArray *placemarks, NSError *error) {
    if (error) {
        NSLog(@"%@", error);
    } else {
        CLPlacemark *placemark = [placemarks lastObject];
        NSLog(@"緯度:%f, 經度:%f", placemark.location.coordinate.latitude, placemark.location.coordinate.longitude);
    }
}];

參考來源:

  1. http://stackoverflow.com/questions/12599316/i-want-to-get-the-location-name-from-the-coordinate-value-in-mapkit-for-iphone
  2. http://stackoverflow.com/questions/18767351/find-latitude-and-longitude-using-clgeocoder

iOS 筆記》UIWebView 調用 JavaScript 函數時,呼叫 Objective-C 方法

有時候開發 iOS App 時,因為某些考量,會讓整個頁面使用 HTML 頁面,而不是使用原生的 iOS 畫面,但是此 HTML 頁面又必須與原生的 code 互動。例如:我們現在有一個會員註冊頁面使用 HTML 頁面,當註冊成功時,必須通知 App 跳轉頁面,這時我們可以讓 HTML 頁面在註冊成功時,呼叫一個叫做 RegisterOK() 的 JavaScript 函數,當這個函數被調用時,我們呼叫一個 Objective-C 函數或是方法,然後開啟其他的 iOS 頁面,這時可以這麼做:

先包含 JavaScriptCore.h 標頭檔:

#import <JavaScriptCore/JavaScriptCore.h>

使用 JavaScriptCore 跟 UIWebView 互動:

#pragma mark - UIWebView Delegate

//...

- (void) webViewDidFinishLoad:(UIWebView *)webView {
    // 透過 KVC 取得 JavaScriptContext
    JSContext *context =  [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
    context[@"RegisterOK"]= ^() {
        NSLog(@"Call RegisterOK()");
        // 執行一些動作,例如跳轉頁面:
        //...
    };
}

參考來源:http://stackoverflow.com/questions/28827317/how-to-listen-to-in-objective-c-uiwebview-when-the-javascript-function-is-trigge

Objetive-C 筆記》Block & Closure

Apple Inc. 為 C、C++、Objective-C 等語言加入了 Block 這個非標準的擴充功能。
簡單來說,Block 是一種可以當作變數傳遞的函數,而且具有 Closure(閉包)特性。

Closure 是指一個包裹了區域變數的函數物件。

1. Block Literal:

Block Literal 的語法:

^ 返回值型別 (參數列) { 表達式 }

其中的返回值型別可以省略不寫,如果該 Block 沒有使用到參數,那麼參數列也可以省略不寫。
所以,完整的 Block 寫法會長得像這樣:

^int (int a, int b) { return a + b; }

如果省略返回值型別不寫,那麼編譯器會使用表達式裡 return 的型別來當作返回值型別;如果表達式裡沒有 return 語句,那返回值型別就會使用 void。所以,上面那個 Block 的寫法可以再簡化成:

^(int a, int b) { return a + b; }

另外,沒有參數列的 Block 可以這樣寫:

^{ NSLog(@"Hello!"); }

2. Block 變數的型別宣告:

Block 變數的型別宣告與函數指標的宣告長得很像,只是把變數名稱前的 * 換成 ^ 而已,所以,Block 變數宣告的語法為:

返回值型別 (^變數名稱) (參數列)

因此,Block 變數宣告的寫法會像這樣:

void (^foo)(void)

宣告 Block 變數,並且賦值:

// 無參數、無返回值的 Block:
void (^blk1)(void) = ^{ NSLog(@"I'm blk1"); };

// 有參數、有返回值的 Block:
int (^blk2)(int, int) = ^(int a, int b) { return a + b; };

3. 使用 Block

Block 的使用方式就如同函數呼叫:

int (^add)(int, int) = ^(int a, int b) { return a + b; };
int sum = add(10, 20);  // call add()
NSLog(@"sum = %d", sum);

執行結果:

sum = 30

或者,可以直接調用一個匿名 Block:

^(int a, int b) {
    NSLog(@"=> %d", a + b);
}(11, 22);

執行結果:

=> 33

4. __block 修飾符

Block 可以捕捉外層範疇(scope)內的變數,所以可以這麼使用:

int number = 100;
void (^blk)(void) = ^{ NSLog(@"=> %d", number); };
blk();

執行結果:

=> 100

但是,如果想修改捕捉到的變數,會發生編譯錯誤,例如:

int number = 100;
void (^blk)(void) = ^{ NSLog(@"=> %d", ++number); /*把 number 加 1*/ };  // Error!
blk();

錯誤訊息如下:

Variable is not assignable (missing __block type specifier)

這時就必須在要修改的變數前面加上 __block 修飾符:

__block int number = 100;  // 前面加上 __block
void (^blk)(void) = ^{ NSLog(@"=> %d", ++number); };
blk();

執行結果:

=> 101

Q:如果是 Cocoa 物件也需要加上 __block 才能修改嗎?

請看以下程式碼:

NSMutableArray *array = [NSMutableArray array];
^{
    [array addObject:@1];
    [array addObject:@2];
}();
NSLog(@"%@", array);

執行結果:

(
  1,
  2
)

沒有編譯錯誤,輸出結果也正確。

再來看看以下程式碼:

NSMutableArray *array = [NSMutableArray array];
^{
    array = [NSMutableArray arrayWithArray:@[@1, @2, @3]];  // Error!
}();
NSLog(@"%@", array);

發生編譯錯誤,錯誤訊息如下:

Variable is not assignable (missing __block type specifier)

加上 __block 修飾符即可通過編譯:

__block NSMutableArray *array = [NSMutableArray array];
^{
    array = [NSMutableArray arrayWithArray:@[@1, @2, @3]];
}();
NSLog(@"%@", array);

執行結果:

(
  1,
  2,
  3
)

因為 Block 捕捉到的是指向物件的指標(看到物件宣告時前面那個*,就應該知道那是個指標 😛 ),所以如果想修改指標的值,就需要加上 __block 修飾符。

接著再看看以下程式碼:

int count = 0;  
void (^blk)(void) = ^{
    NSLog(@"-> %d", count);
};
count = 100;
NSLog(@"%d", count);   //印出目前 count 變數的值 
blk();                 //調用 blk

執行結果:

100
-> 0

調用 blk() 後印出的結果是 0,而不是 100。

因此,我們可以這麼認為:

在沒有使用 __block 修飾符的情況下:
對於內建型別(int, float…等),Block 捕捉到的是變數的
對於物件型別,Block 捕捉到的是物件的參考

5. 使用 typedef 定義 Block 型別

如果想把 Block 當作參數傳給函數,可以這麼寫:

void func(int(^add)(int, int)) {
    NSLog(@"sum = %d", add(10, 20));
}

int main() {
    @autoreleasepool {
        func(^(int a, int b){ return a + b; });
    }
    return 0;
}

執行結果:

sum = 30

如果想把一個 Block 當作函數返回值傳回呢?可以這麼寫:

int (^makeAdd(int c))(int, int) {
    return ^(int a, int b) { return a + b + c; };
}

int main() {
    @autoreleasepool {
        int (^add)(int, int) = makeAdd(1000);
        NSLog(@"=> %d", add(11, 22));
    }
    return 0;
}

執行結果:

=> 1033

看到這些寫法,頭都開始暈了@@…

這時可以使用 typedef 定義 Block 型別,讓程式碼比較好寫好讀:

typedef int (^AddBlock)(int a, int b);

void func(AddBlock add) {
    NSLog(@"=> %d", add(5500, 66));
}
  
AddBlock makeAdd(int c) {
    return ^(int a, int b) { return a + b + c; };
}
  
int main() {
    @autoreleasepool {
        func(^(int a, int b){ return a + b; });
        AddBlock add = makeAdd(5500);
        NSLog(@"=> %d", add(11, 55));
    }
    return 0;
}

執行結果:

=> 5566
=> 5566

(56 不能亡!)

6. Block 的範疇(scope)與記憶體管理

Block 有三種類型,分別為_NSConcreteGlobalBlock_NSConcreteStackBlock_NSConcreteMallocBlock

寫在全域空間(即函數外面)的 Block 是 _NSConcreteGlobalBlock 類別的一個實例,會儲存在 globals 區段裡。Global Block 因為是寫在全域空間,所以無法捕捉區域變數,且從程式開始執行一直到程式結束前都會存在,因此可以視為是一個單體物件(Singleton object)。

寫在函數或是程式碼區塊(即一對{})內的 Block 一般會儲存在 stack 區段,但是如果該 Block 並沒有使用到外層範疇內的變數(換句話說,就是不需要捕捉外層的區域變數),那麼該 Block 會被儲存在 globals 區段裡(_NSConcreteGlobalBlock 類別的實例)。

儲存在 stack 區段的 Block 是 _NSConcreteStackBlock 類別的實例。如果對一個 Stack Block 物件發送 copy 訊息,那麼該 Block 會被拷貝到 heap 區段上,儲存在 heap 區段上的 Block 是 _NSConcreteMallocBlock 類別的實例。

Stack Block 就跟自動變數一樣,會在離開其生存空間時被移除,如果想在生存空間外繼續使用,就必須拷貝到 heap 區段上。儲存在 heap 區段上的 Block 如果不再使用,那麼要對它發送 release 訊息釋放其所佔用的記憶體空間。

如果程式的記憶體管理方式是使用 ARC(Automatic Reference Counting),那麼大多不用擔心 Block 何時需要 copy/release,因為編譯器會在必要時自動拷貝或釋放 Block。如果記憶體管理方式是使用 MRR(Manual Retain-Release),那麼必須自行管理 Block 的 copy/release 訊息。

先來看看以下程式碼:

int main() {
    @autoreleasepool {
        void(^blk)(void) = ^{ NSLog(@"Hello!"); };  //這是一個 Global Block
        blk();
    }
    return 0;
}

blk 是一個 Global Block,因為它沒捕捉任何變數,也就是說,它不需要在執行時期設置任何狀態,所以被編譯成一個 Global Block。

那麼,以下這段程式碼裡的 blk2 會被編譯成什麼類型的Block呢?:

int main() {
    @autoreleasepool {
        int i = 100;
        void(^blk2)(void) = ^{ NSLog(@"i = %d", i); }; //這個 Block 會被編譯成什麼類型呢?
        blk2();
    }
    return 0;
}

如果是在 ARC 的情況下,那 blk2 會是 Malloc Block
如果是在 MRR 的情況下,那 blk2 會是 Stack Block

在 MRR 的情況下,函數內宣告的 Block 會是 Stack Block。問題來了,請看以下程式碼:

typedef void(^blk_t)(void);

blk_t func() {
    int i = 10;
    return ^{ NSLog(@"i = %d", i); };
}

int main() {
    @autoreleasepool {
        blk_t blk = func();
        blk();
    }
    return 0;
}

上面這段程式碼在 ARC 下,可以正常編譯且執行。但在 MRR 下,會發生編譯錯誤,錯誤訊息如下:

Returning block that lives on the local stack

這時必須把 Block 拷貝到 heap 區段上才行:

typedef void(^blk_t)(void);

blk_t func() {
    int i = 10;
    return [[^{ NSLog(@"i = %d", i); } copy] autorelease];  // copy and autorelease
}

再來看一些在 MRR 下,需要自行 copy/release 的情況:

/** 
 * 將 Block 存入 NSArray/NSDictionary 前,
 * 需要把 Block 從 stack 區段拷貝到 heap 區段上。
 *
 * 註:
 *   NSArray/NSDictionary 只能存放 Objective-C 物件,
 *   而所有的 Objective-C 物件都是配置在 heap 區段上的。
 */
NSMutableArray *array = [NSMutableArray array];
[array addObject:[^(int a){NSLog(@"%d", a);} copy]];
// Block 離開生存空間後要繼續使用,需要 copy,使用完要 release
void(^myBlock)(void);
if (isTrue) {
    //...
    myBlock = [^{ /* ... */ } copy];
} else {
    //...
    myBlock = [^{ /* ... */ } copy];
}
//...
myBlock();
[myBlock release];

總之,如果是使用 ARC 管理記憶體,那麼不用擔心 Block 拷貝和釋放的問題,一切就交給編譯器處理吧。
如果是用 MRR 管理記憶體,那麼請務必留意 Block 何時需要拷貝,何時需要釋放。

7. Block 保留循環的問題

之前有提到,Block 可以捕捉外層範疇內的變數。
如果捕捉到的變數是一個 Objective-C 物件,那麼該物件會被保留(retain),等到 Block 執行完畢後,該物件會被釋放(release)。

假設有一個 ViewController,這個 ViewController 有一個 property 是 Block 物件,程式碼看起來像這樣:

typedef void (^MyCompletionBlock)(void);

@interface MyViewController : UIViewController
@property (nonatomic, copy) MyCompletionBlock completionBlock;
//...
@end

之後,在程式當中使用了這個 ViewController:

MyViewController *myViewController = [MyViewController alloc] init];

myViewController.completionBlock = ^{
    [myViewController doSomething];
};

有看出上面這段程式碼有什麼問題嗎?這段程式碼裡發生了保留循環(或稱為強參考循環 (Strong Reference Cycles) )! 因為 myViewController 保留了 completionBlock,而 completionBlock 也保留了 myViewController,所以形成了保留循環。

產生保留循環表示這些互相保留對方的物件將無法被釋放,會一直佔用記憶體空間,即使這些物件已不再使用。這種情況稱為記憶體洩漏

ARC 下的解決方法是使用一個弱參考(Weak reference)指標指向 myViewController,並在 Block 裡使用這個弱參考指標取代原本的強參考(Strong reference)指標:

// 如果記憶體管理方式是使用 ARC,可以用這種方式解決強參考循環:
MyViewController *myViewController = [MyViewController alloc] init];
__weak MyViewController *weakMyViewController = myViewController;

myViewController.completionBlock = ^{
    [weakMyViewController doSomething];
};

MRR 下的解決方式:

// 在 MRR 下使用 __block 修飾符,表示這個物件的參考計數不要增加
__block MyViewController *myViewController = [MyViewController alloc] init];

myViewController.completionBlock = ^{
    [myViewController doSomething];
    myViewController = nil;
};

另外,附帶一提:Block 執行完畢後,如果 Block 不需要再使用,將 Block 釋放可能是一個好習慣:

if (self.completionBlock) {
    self.completionBlock();
    self.completionBlock = nil;
}

8. Closure

最常見的 Closure 範例應該就是計數器了吧。先用 JavaScript 示範計數器要怎麼寫吧:

// JavaScript 的計數器寫法


function makeCounter() {
  var n = 0;
  return function() {
    n++;
    console.log("n = " + n);
  }
}

f = makeCounter();  // 傳回一個函數物件

f();                // 調用 f() 三次...
f();
f();

執行結果:

n = 1
n = 2
n = 3

注意 makeCounter() 函數裡 n 變數的生命週期。照理說,n 變數應該在 makeCounter() 結束後就應該消失了,但是為何 n 變數可以持續累加呢?因為這是 Closure(閉包)造成的效果。

makeCounter() 會回傳一個函數物件,而這個物件會將它定義當時範疇內的變數與函數繫結在一起。換句話說,就是會將該函數物件上層範疇內的區域變數「封閉」到該函數物件裡,因此稱為 Closure(閉包)。

所以,這就是 n 變數繼續存活的原因。

接著來把這段程式碼改成 Objective-C 版本:

// Objective-C 的計數器寫法
typedef void(^CounterBlock)(void);

CounterBlock makeCounter() {
    __block int n = 0;
    return ^{ NSLog(@"n = %d", ++n); };
}
  
int main() {
    @autoreleasepool {
        CounterBlock counter = makeCounter();
        counter();
        counter();
        counter();
    }
    return 0;
}

執行結果:

n = 1
n = 2
n = 3

再來看一個 Closure 範例:

typedef NSString* (^SayBlock)(NSString *sentence);

SayBlock whoSay(NSString *name) {
    return ^(NSString *sentence) { return [NSString stringWithFormat:@"%@ say: %@", name, sentence]; };
}

int main() {
    @autoreleasepool {
        SayBlock johnSay = whoSay(@"John");
        SayBlock marySay = whoSay(@"Mary");

        NSLog(@"%@", johnSay(@"Hello!"));
        NSLog(@"%@", marySay(@"Hi!"));
        NSLog(@"\n");
        NSLog(@"%@", johnSay(@"ABCD狗咬豬!"));
        NSLog(@"%@", marySay(@"1234567..."));
    }
    return 0;
}

執行結果:

John say: Hello!
Mary say: Hi!

John say: ABCD狗咬豬!
Mary say: 1234567...

在第一個範例裡用了 JavaScript 來示範計數器的寫法,再來看看一個 JavaScript 範例吧(但是跟 Closure 無關… XD):

var op = {
  "+" : function(a,b) { return a + b; },
  "-" : function(a,b) { return a - b; },
  "*" : function(a,b) { return a * b; },
  "/" : function(a,b) { return a / b; }
};

op 是一個 JavaScript 物件,JavaScript 的物件是鍵值對的集合,也就是一般俗稱的 Hash 或是 Dictionary。
然後可以這樣使用 op 物件:

var sum = op["+"](100, 20);

sum 的值是 120。

如果想在 Objective-C 裡實現這種寫法呢?可以這麼寫:

NSDictionary *operation = @{ @"+" : ^(int a, int b){return a + b;},
                             @"-" : ^(int a, int b){return a - b;},
                             @"*" : ^(int a, int b){return a * b;},
                             @"/" : ^(int a, int b){return a / b;} };

然後來試著調用 Key 為 @”+” 的 Block:

int sum = operation[@"+"](100, 20);

結果發生編譯失敗,錯誤訊息如下:

Called object type 'id' is not a function or function pointer

因為無法對一個id型別進行函數呼叫的動作。
所以,必須先將 id 轉型為 Block,然後再調用它:

int sum = ((int(^)(int,int))operation[@"+"])(100, 20);

sum 的值為 120。
但是這個寫法真的很醜啊~~~~~

另一個寫法:

int (^add)(int, int) = operation[@"+"];
int sum = add(100, 20);

這個寫法看起來好多了,但是有點囉唆…

同場加映:C++ 的寫法:

map<string, function<int (int,int)>> operation = {
    { "+", [](int a, int b) {return a + b;} },
    { "-", [](int a, int b) {return a - b;} },
    { "*", [](int a, int b) {return a * b;} },
    { "/", [](int a, int b) {return a / b;} }
};
    
int a = 100, b = 20;
string opcodes[4] = {"+", "-", "*", "/"};
  
for (auto opcode : opcodes) {
    cout << a << " " << opcode << " " << b << " = " << operation[opcode](a, b) << endl;
}

執行結果:

100 + 20 = 120
100 - 20 = 80
100 * 20 = 2000
100 / 20 = 5

個人認為,C++ 的 Lambda 表達式寫法看起來比 Objective-C Block 好看多了…

9. 對 Block 的愛與怨

對 Block 的愛:
身為一個 Ruby 程式員(自以為,但其實沒寫過幾行 Ruby 程式啊… XD),當然會對 Block 很有愛呀(羞) >////<
(旁白哥:可是 Ruby 的 Block 跟 Objective-C 的 Block 好像不太一樣呀… )*

對 Block 的怨:
「話那欲講透更,目屎是掰袂離…」 所以,不說也罷(逃~)。

*註:Objective-C 的 Block 行為上跟 Ruby 的 lambda 比較像。

Objective-C 筆記》貓王運算符

有一種常見的三元運算符用法如下:

NSString *aString = nil;
NSString *outString;

outString = aString ? aString : @"";   //=> outString: ""

aString = @"Hello";
outString = aString ? aString : @"";  //=> outString: "Hello"

對於這種三元運算符用法,GCC C Extension 增加了可省略中間運算式的 ?: 運算符來縮短這種寫法,用法如下:

NSString *aString = nil;
NSString *outString;

outString = aString ?: @"";  //=> outString: ""

aString = @"Hello";
outString = aString ?: @"";  //=> outString: "Hello"

我在《 Java 程序員修煉之道》這本書裡看到這種運算符稱作貓王運算符(Elvis operator)

因為這個符號看起來明顯很像貓王鼎盛時期梳的大背頭

重點來了,我寫這篇筆記只是想說:貓王運算符這個名字真是太可愛了! >////<

Objective-C 筆記》GCC 的 C 擴充功能: Code Block Evaluation

在 NSHipster 看到 Clang 有支援 GCC 的擴充功能:Code Block Evaluation,看到後覺得真是驚為天人(又在亂用成語了XD),這個功能真是太實用了,立馬就在正在寫的專案裡用了這種寫法 😀

這個功能是用圓括弧()包起一個代碼區塊{},然後這個代碼區塊最後一個表達式的值就是返回的值,寫法就像這樣:

self.nameLabel = ({
    UILabel *label = [UILabel alloc] initWithFrame:CGRectMake(10, 10, 100, 44)];
    label.text = @"Riddle";
    label.textColor = [UIColor greenColor];
    label;
});

self.blogLabel = ({
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 50, 300, 40)];
    label.text = @"http://riddleapple.logdown.com";
    label.textColor = [UIColor redColor];
    label;
});

因為變數只能存活在宣告那個變數的大括號內,因此可以利用這個方式,重複使用一些通用的變數名稱而不會產生衝突!