分類彙整: Objective-C

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;
});

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

Objective-C 筆記》NSSelectorFromString

在 Objective-C 裡,可以使用 NSSelectorFromString() 函式把一個 NSString 轉換成 Selector,這樣就可以在執行期才決定要傳送的訊息:

SEL aSelector = NSSelectorFromString(@"redColor");  // 把一個字串轉換成 Selector

例如,有一個儲存顏色字串的 Array :

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // color string array
        self.items = @[@"Red", @"Green", @"Blue", @"Yellow", @"Orange"];
    }
    return self;
}

然後在一個 TableView 裡顯示這些顏色字串與顏色:

#pragma mark - UITableView Data Source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return _items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }
    
    // 利用字串產生 Selector
    SEL colorMethod = NSSelectorFromString([NSString stringWithFormat:@"%@Color", [_items[indexPath.row] lowercaseString]]);

    if ([UIColor respondsToSelector:colorMethod]) {
        // 發送訊息
        cell.textLabel.textColor = [UIColor performSelector:colorMethod];
    }
    
    cell.textLabel.text = _items[indexPath.row];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;

    return cell;
}

執行結果會像這樣:

Objective-C 筆記》使用 GCD 的 dispatch_apply 函數實現類似 Ruby 的 map 方法

Ruby 語言的 Array 類別有一個 map 方法,可以把一個 Block 應用到 Array 裡的每個元素上,並將 Block 的傳回值收集成一個新的 Array,例如:

a = ['a', 'b', 'c']
a.map {|e| e.upcase}   # => ['A', 'B', 'C']

在 Objective-C 裡可以利用 GCD 的 dispatch_apply 函數來實現 map 方法:

#import <Foundation/Foundation.h>

@interface NSArray (RAMethods)
- (NSArray *)map:(id (^)(id item))block;
@end
#import "NSArray+RAMethods.h"

@implementation NSArray (RAMethods)

- (NSArray *)map:(id (^)(id item))block {
    NSMutableArray *results = [NSMutableArray array];
    for (int i = 0; i < self.count; ++i) {
        [results addObject:[NSNull null]];
    }
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_apply(self.count, queue, ^(size_t index) {
        [results replaceObjectAtIndex:index withObject:block([self objectAtIndex:index])];
    });
    
    return results;
}
@end

使用方式:

#import "NSArray+RAMethods.h"
...

NSArray *array = @[@"a", @"b", @"c", @"d", @"e"];
NSArray *tmpArray = [array map:^(id item) {
                        NSLog(@"%@", item);
                        return [item uppercaseString];
                    }];
NSLog(@"----------");
NSLog(@"tmpArray: %@", tmpArray);

輸出的結果:

a
c
d
e
b
----------
tmpArray: (
    A,
    B,
    C,
    D,
    E
)

dispatch_apply 函數有三個參數:第一個參數是迭代次數,第二個參數是要使用的 Dispatch Queue ,第三個參數是要執行的 Block。

可以注意到在輸出結果中,虛線上方的字母次序跟 array 裡的次序不同,這是因為 dispatch_apply 函數會把第三個參數的 Block 加到 Dispatch Queue 裡,各個 Block 的執行時間不定,所以虛線上方的字母次序才會跟 array 裡的不同。

iOS 筆記》UIColor

每次使用 UIColor 的 +colorWithRed:green:blue:alpha: 方法都要把 RGB 數值換算成 0 ~ 1.0 間的浮點數,覺得太麻煩了,可以使用下面這個方法,給一組 RGB 十進制數值的陣列:

#import <UIKit/UIKit.h>

@interface UIColor (RAMethods)
+ (UIColor *)colorWithRGB:(NSArray *)array alpha:(CGFloat)alpha;
@end
#import "UIColor+RAMethods.h"

@implementation UIColor (RAMethods)

+ (UIColor *)colorWithRGB:(NSArray *)array alpha:(CGFloat)alpha {
    if (array.count != 3) {
        return nil;
    }
    
    NSMutableArray *results = [NSMutableArray array];
    
    for (NSNumber *number in array) {
        float colorValue = ((float)[number intValue])/255;
        [results addObject:[NSNumber numberWithFloat:colorValue]];
    }

    return [self colorWithRed:[results[0] floatValue]
                        green:[results[1] floatValue]
                         blue:[results[2] floatValue]
                        alpha:alpha];
}
@end

使用方式:

#import "UIColor+RAMethods.h"

...

// R: 127, G: 127, B: 127 (灰色)
UIColor *color = [UIColor colorWithRGB:@[@127, @127, @127] alpha:1.0f];

// R:255, G: 0, B: 0 (紅色)
UIColor *color = [UIColor colorWithRGB:@[@255, @0, @0] alpha:1.0f];