2014年7月27日 星期日

iOS 開發筆記 - Admob SDK: Must set the rootViewController property of GADBannerView before calling loadRequest

又有一陣子沒把玩 Admob 了 XD 每次把玩的方式不同,這次看到錯誤訊息:
<Google> Must set the rootViewController property of GADBannerView before calling loadRequest:
以及:
didFailToReceiveAdWithError:Error
其他訊息:
<Google> To get test ads on this device, call: request.testDevices = @[ GAD_SIMULATOR_ID ];
現況與解法:

@interface YourViewController() <GADBannerViewDelegate>

@property (nonatomic, strong) GADBannerView *banner;

@end

@implementation YourViewController

- (GADBannerView *)banner {
    if (!_banner) {
        // 468*60
        _banner = [[GADBannerView alloc] initWithAdSize:kGADAdSizeFullBanner];
        _banner.adUnitID = @"ca-app-pub-###############";
    }
    return _banner;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    self.banner.frame = CGRectMake(0, self.view.frame.size.height > self.view.frame.size.width ? self.view.frame.size.width - 60 : self.view.frame.size.height - 60, 468, 60 );
    self.banner.delegate = self;
    [self.view addSubview:self.banner];

    // <Google> Must set the rootViewController property of GADBannerView before calling loadRequest:
    self.banner.rootViewController = self;

    GADRequest* req = [GADRequest request];
    // <Google> To get test ads on this device, call: request.testDevices = @[ GAD_SIMULATOR_ID ];
    req.testDevices = @[GAD_SIMULATOR_ID];
    [self.banner loadRequest:req];


    NSLog(@"self.banner: %@", self.banner);
}

- (void)adViewDidReceiveAd:(GADBannerView *)view
{
    NSLog(@"adViewDidReceiveAd");
}

- (void)adView:(GADBannerView *)view
didFailToReceiveAdWithError:(GADRequestError *)error
{
    NSLog(@"didFailToReceiveAdWithError:%@", error);
}

@end

iOS 開發筆記 - 使用 CocoaPods 與 Google Admob iOS SDK

繼續用 CocoaPods 練功,順手弄一下 Google Mobile Admob iOS SDK 6.10.0 版(changyy/GoogleMobileAdsSdkiOS.git),有需要可以從這邊使用:

$ vim Podfile
pod 'GoogleMobileAdsSdkiOS', :git => 'https://github.com/changyy/GoogleMobileAdsSdkiOS.git'


不過在 GoogleMobileAdsSdkiOS.podspec 這邊關於 authors, license 填寫反而是個問題 Orz 不知哪邊有好的介紹方式,暫時參考前輩們的用法。

此次撰寫 GoogleMobileAdsSdkiOS.podspec 時,花了不少時間在測試,主因是想要用 symbolic link 來管理版本更新的,例如建立 GoogleMobileAdsSdkiOS-latest 指向最新版版,藉以降低 podspec 的更動頻率:

project:
  • $ ln -s GoogleMobileAdsSdkiOS-6.10.0 GoogleMobileAdsSdkiOS-latest
podspec:
  • s.source_files = 'GoogleMobileAdsSdkiOS-latest/*.{h}'
  • s.preserve_paths = 'GoogleMobileAdsSdkiOS-latest'
  • s.xcconfig = { 'LIBRARY_SEARCH_PATHS' => "$(PODS_ROOT)/GoogleMobileAdsSdkiOS/GoogleMobileAdsSdkiOS-latest/**" }
結果發現 s.preserve_paths 就只會 copy 那則 symbolic link 而已,也指不到東西 XD  雖然可以添加  GoogleMobileAdsSdkiOS-6.10.0 來解決,但 s.source_files 那邊似乎不支援 symbolic link 用法,直接去看 Pods/Header 沒法看到產生,這導致無法正常 import header 來使用,最後只好作罷,變成每次更新版本時,要更動與版本敘述相關的資料了( s.source_files、s.preserve_paths 跟 s.scconfig)

2014年7月25日 星期五

[SQL] 依據條件取出指定欄位 SELECT IF/ELSE, CASE WHEN/ELSE 用法 @ MySQL 5.6

假設有一張 Table 有兩個欄位:

mysql> SELECT * FROM mtable;
+----+----+
| f1 | f2 |
+----+----+
| 1  | 2  |
| 4  | 3  |
| 5  | 6  |
+----+----+


想要撈出 f1 跟 f2 之中,數值最大者:

mysql> SELECT f1 AS result FROM mtable WHERE f1 > f2;
+--------+
| result |
+--------+
| 4      |
+--------+

mysql> SELECT f2 AS result FROM mtable WHERE f1 < f2;
+--------+
| result |
+--------+
| 2      |
| 6      |
+--------+


這時候,可透過條件判斷,透過 CASE WHEN/ELSE 的用法,就可以不用分兩次撈了:

mysql> SELECT
CASE WHEN f1 > f2
THEN f1
ELSE f2
END AS result
FROM mtable;
+--------+
| result |
+--------+
| 2      |
| 4      |
| 6      |
+--------+

2014年7月23日 星期三

iOS 開發筆記 - 使用 GCDAsyncSocket / AsyncSocket 之處理 Partial Data 的用法

原本想寫寫 BSD Socket 的,再搭配 dispatch_async 其實也能運作的很好,但讓我想起去年底面試一間新創時,被問到有沒有用過 GCDAsyncSocket 的心得,看來用用已經常見的 framework / library 已是一種流行的或組織文化吧?至少接手的人可以有一樣的思維而不用再去認識另一個 coding style 吧!

把玩 GCDAsyncSocket 時,一開始試用了 readDataToData 函數,但發現這東西不是我想要的,也就是 server 沒有吐出預設字元(newline)時,程式端無法處理已接收的資料,但可以得知已經接收多少 Partial Data (可用在回報下載、上傳進度)。爾後用一些關鍵字(partial data)找找,發現也有人有這種需求:Is there a way to get partial data read and clear the buffer?

原來會有這種困惑只是不懂 GCDAsyncSocket 設計架構,搞懂後就一切安好啦。

There are 3 methods of reading data using GCDAsyncSocket / AsyncSocket:
  • readDataWithTimeout : reads chunks of data as they arrive
  • readDataToLength : read a specific length of data before returning result
  • readDataToData : read until finding a specific terminator

對於不能確定 Server 回傳的資料長度或特徵的行為,就適合採用 readDataWithTimeout 方式 :)

- (GCDAsyncSocket*)asyncSocket {
if(!_asyncSocket)
_asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_queue_create("socket", NULL)];
return _asyncSocket;
}

- (NSMutableData *)buffer {
if (!_buffer) {
_buffer = [[NSMutableData alloc] init];
}
return _buffer;
}

- (void)viewDidLoad {
[super viewDidLoad];

if (![self.asyncSocket connectToHost:@"ServerIP" onPort:80 error:&error]) {
NSLog(@"ERROR: %@", error);
}
}

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
NSLog(@"didConnectToHost");
[sock readDataWithTimeout:-1 buffer:self.buffer bufferOffset:[self.buffer length] tag:1];
}

- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag {
NSLog(@"didReadPartialDataOfLength");
}

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSLog(@"didReadData(%lu): %@", tag, @([data length]));
[self.buffer setLength:0];
[sock readDataWithTimeout:-1 buffer:self.buffer bufferOffset:[self.buffer length] tag:tag];
}

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
NSLog(@"socketDidDisconnect: %@", err);
}

- (void)socketDidSecure:(GCDAsyncSocket *)sock {
NSLog(@"socketDidSecure");
}

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSLog(@"didWriteDataWithTag");
}

iOS 開發筆記 - 使用 UITapGestureRecognizer / UISwipeGestureRecognizer 偵測 UIView Click/Tap/Swipe Event (手指點擊、滑動事件)

沒把玩過,趁颱風天筆記一下 :P

One Click (Single Tap):

- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *tapGestureRecognizer;
tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)];
tapGestureRecognizer.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tapGestureRecognizer];
}

- (void)singleTap:(UIGestureRecognizer *)recognizer {
NSLog(@"singleTap: %@", NSStringFromCGPoint([recognizer locationInView:[recognizer.view superview]]));
}


Double Click (Double Tap):

- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *tapGestureRecognizer;
tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
tapGestureRecognizer.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:tapGestureRecognizer];
}

- (void)doubleTap:(UIGestureRecognizer *)recognizer {
NSLog(@"doubleTap: %@", NSStringFromCGPoint([recognizer locationInView:[recognizer.view superview]]));
}


Swipe Up:

- (void)viewDidLoad {
[super viewDidLoad];
UISwipeGestureRecognizer *swipeGestureRecognizer;
swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeUp:)];
swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionUp;
[self.view addGestureRecognizer:swipeGestureRecognizer];
}

- (void)swipeUp:(UIGestureRecognizer *)recognizer {
NSLog(@"swipeUp: %@", NSStringFromCGPoint([recognizer locationInView:[recognizer.view superview]]));
}


Swipe Down:

- (void)viewDidLoad {
[super viewDidLoad];
UISwipeGestureRecognizer *swipeGestureRecognizer;
swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeDown:)];
swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionDown;
[self.view addGestureRecognizer:swipeGestureRecognizer];
}

- (void)swipeDown:(UIGestureRecognizer *)recognizer {
NSLog(@"swipeDown: %@", NSStringFromCGPoint([recognizer locationInView:[recognizer.view superview]]));
}


Swipe Left:

- (void)viewDidLoad {
[super viewDidLoad];
UISwipeGestureRecognizer *swipeGestureRecognizer;
swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeLeft:)];
swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:swipeGestureRecognizer];
}

- (void)swipeLeft:(UIGestureRecognizer *)recognizer {
NSLog(@"swipeLeft: %@", NSStringFromCGPoint([recognizer locationInView:[recognizer.view superview]]));
}


Swipe Right:

- (void)viewDidLoad {
[super viewDidLoad];
UISwipeGestureRecognizer *swipeGestureRecognizer;
swipeGestureRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeRight:)];
swipeGestureRecognizer.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:swipeGestureRecognizer];
}

- (void)swipeRight:(UIGestureRecognizer *)recognizer {
NSLog(@"swipeRight: %@", NSStringFromCGPoint([recognizer locationInView:[recognizer.view superview]]));
}


其他部分,大概是注意 UIView 跟 UIViewController 內定已經有把 events 接收處理的事項,例如 UIScrollView 就有捲動事件,假設 UIScrollView 只有提供左右捲動的功能,那就偵測不到 SwipeLeft 跟 SwipeRight ,但 SwipeUp 跟 SwipeDown 仍可以。而 Single Tap 跟 Double Tap 之間,基本上兩者存在時,點擊兩下的過程中,Single Tap Action 跟 Double Tap Action 都會被執行,並且 Single Tap Action 會先被執行。

2014年7月22日 星期二

[CPP] Incremental Test Case for C++ Usage via Google Test @ Ubuntu 14.04

既然做了 Unit Test 了,就希望 Test Case 可以不斷地累積下來。昨晚跟總監級的高手閒聊工作瑣事,以 Python 跟 MongoDB 的互動為例,其實 PyMono 很多操作都還是撰寫 Javascript ,只是從 Python 發動罷了,這時候最佳的設計是讓 Javascript 獨立出來,如此一來可以切割工作出來,讓把玩 Javascript 就專心把玩,而不要每次要改 Javascript 時,還得去動 Python code。

這樣的情境跟 Test Case 有點類似,有沒有辦法讓 Test case 增加時,不必動到測試的邏輯程式?解法就是把 Test Case 用目錄管理,每次做 Unit Test 時,是去掃目錄的檔案出來即可。

片段程式碼:

#include <dirent.h>
bool getFiles(std::string dir, std::vector<std::string> &files) {
        DIR *dp;
        struct dirent *dirp;
        if((dp  = opendir(dir.c_str())) == NULL)
                return false;
        while ((dirp = readdir(dp)) != NULL)
                if(dirp->d_name && dirp->d_name[0] != '.')
                        files.push_back(dir+"/"+std::string(dirp->d_name));
        closedir(dp);
        return true;
}

const std::string testcase_dir_Func1 = "testcase_dir";
TEST(MyJob, testFunc1) {
        std::vector<std::string> testcase;
        std::string dirTarget = testcase_dir_Func1;
        ASSERT_EQ(getFiles(dirTarget,testcase), true);
        ASSERT_EQ(testcase.size() > 0, true);
        for(int i=0 ; i<testcase.size() ; ++i) {
                std::fstream input(testcase[i], std::fstream::binary|std::fstream::in);
                std::stringstream buffer;
                buffer << input.rdbuf();
                ASSERT_EQ(buffer.str().length() > 10, true);

// ...
        }
}

2014年7月21日 星期一

[CPP] Unit Test for C++ Usage via Google Test @ Ubuntu 14.04

跟友人閒聊把玩 CPP 的心得,就被推坑到 Google test 啦:Google C++ Testing Framework

看一下簡介就...準備跳槽了 XD

Who Is Using Google Test?

In addition to many internal projects at Google, Google Test is also used by the following notable projects:
  • The Chromium projects (behind the Chrome browser and Chrome OS)
  • The LLVM compiler
  • Protocol Buffers (Google's data interchange format)
安裝:

$ sudo apt-get install cmake libgtest-dev
$ mkdir gtest && cd gtest
$ cmake /usr/src/gtest/
-- The CXX compiler identification is GNU 4.8.2
-- The C compiler identification is GNU 4.8.2
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Found PythonInterp: /usr/bin/python (found version "2.7.6")
-- Looking for include file pthread.h
-- Looking for include file pthread.h - found
-- Looking for pthread_create
-- Looking for pthread_create - not found
-- Looking for pthread_create in pthreads
-- Looking for pthread_create in pthreads - not found
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - found
-- Found Threads: TRUE
-- Configuring done
-- Generating done
-- Build files have been written to: /path/gtest

$ make
Scanning dependencies of target gtest
[ 50%] Building CXX object CMakeFiles/gtest.dir/src/gtest-all.cc.o
Linking CXX static library libgtest.a
[ 50%] Built target gtest
Scanning dependencies of target gtest_main
[100%] Building CXX object CMakeFiles/gtest_main.dir/src/gtest_main.cc.o
Linking CXX static library libgtest_main.a
[100%] Built target gtest_main

$ sudo cp *.a /usr/lib


試用:

$ vim gtest.cpp
#include <gtest/gtest.h>

int main(int argc, char** argv) {
        testing::InitGoogleTest(&argc, argv);
        return RUN_ALL_TESTS();
}
$ g++ gtest.cpp -lgtest -lpthread
$ ./a.out
[==========] Running 0 tests from 0 test cases.
[==========] 0 tests from 0 test cases ran. (0 ms total)
[  PASSED  ] 0 tests.


測試失敗範例:

$ vim gtest.cpp
#include <gtest/gtest.h>

TEST(MyJob, Action1) {
    ASSERT_EQ(0, 1);
}

int main(int argc, char** argv) {
        testing::InitGoogleTest(&argc, argv);
        return RUN_ALL_TESTS();
}


$ g++ gtest.cpp -lgtest -lpthread
$ ./a.out

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from MyJob
[ RUN      ] MyJob.Action1
gtest.cpp:4: Failure
Value of: 1
Expected: 0
[  FAILED  ] MyJob.Action1 (1 ms)
[----------] 1 test from MyJob (1 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (1 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] MyJob.Action1

 1 FAILED TEST


測試成功的範例:

$ vim gtest.cpp
#include <gtest/gtest.h>

TEST(MyJob, Action1) {
    ASSERT_EQ(0, 0);
}

int main(int argc, char** argv) {
        testing::InitGoogleTest(&argc, argv);
        return RUN_ALL_TESTS();
}


$ g++ gtest.cpp -lgtest -lpthread
$ ./a.out

[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from MyJob
[ RUN      ] MyJob.Action1
[       OK ] MyJob.Action1 (0 ms)
[----------] 1 test from MyJob (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (1 ms total)
[  PASSED  ] 1 test.

2014年7月19日 星期六

[CPP] Unit Test for C++ Usage via CppUnit @ Ubuntu 14.04

隨著時間越來越少後,不知不覺更在意"成果"是否能夠年年累積。想了一會兒後,最佳的方式就是引進軟工管理,除了最基本的版本控制外,幫自己把玩的小玩意 open source 及增加 reuse 的機會也很重要,維護方面則是從簡易人眼的測試改成更制式的 unit test 架構,最好能夠定時批次測試等。只不過採用制式的 unit test 不見得適合 startup 環境,畢竟時間寶貴,有些 prototype 生命週期很短。

在此以 CppUnit 為例,筆記一下怎樣快速使用,更多簡介請參考 CppUnit 文件

安裝:

$ sudo apt-get install libcppunit-dev

$ vim t.cpp
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/TestResult.h>
#include <cppunit/CompilerOutputter.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/BriefTestProgressListener.h>

class SampleTest : public CppUnit::TestFixture {
private:
int a;
public:
void setUp() {
a = 0;
}
void tearDown() {
a = 0;
}
static CppUnit::Test *suite() {
CppUnit::TestSuite *suiteOfTests = new CppUnit::TestSuite( "SampleTest" );
suiteOfTests->addTest( new CppUnit::TestCaller<SampleTest>(
"testSample",
&SampleTest::testSample
));
return suiteOfTests;
}
void testSample() {
CPPUNIT_ASSERT( a == 10 );
}
};

int main() {
std::cout << "\n\n=== Style 1 ===\n\n";
{
CppUnit::TestCaller<SampleTest> test ( "testSample", &SampleTest::testSample );

CppUnit::BriefTestProgressListener progress;
CppUnit::TestResultCollector collectedresults;
CppUnit::TestResult result;
result.addListener(&progress);
test.run( &result );

CppUnit::CompilerOutputter outputter( &collectedresults, std::cerr );
}

std::cout << "\n\n=== Style 2 ===\n\n";
{
CppUnit::TestSuite suite;
suite.addTest( new CppUnit::TestCaller<SampleTest>(
"testSample",
&SampleTest::testSample
));

CppUnit::BriefTestProgressListener progress;
CppUnit::TestResultCollector collectedresults;
CppUnit::TestResult result;
result.addListener(&progress);
suite.run( &result );

CppUnit::CompilerOutputter outputter( &collectedresults, std::cerr );
}

std::cout << "\n\n=== Style 3 ===\n\n";
{
CppUnit::TextUi::TestRunner runner;
runner.addTest( SampleTest::suite() );
runner.run();
}
return 0;
}


測試成功:

=== Style 1 ===

testSample : OK


=== Style 2 ===

testSample : OK


=== Style 3 ===

.


OK (1 tests)


測試失敗:

=== Style 1 ===

testSample : assertion


=== Style 2 ===

testSample : assertion


=== Style 3 ===

.F


!!!FAILURES!!!
Test Results:
Run:  1   Failures: 1   Errors: 0


1) test: testSample (F) line: 27 t.cpp
assertion failed
- Expression: a == 10


目前大概打算對自定的 class 編寫一些偏邏輯層面的 unit test,例如 bool testFunc1(); 的方式,成功就回傳 true,但不套用任何 unit test framework,如此一來純邏輯的驗證更可以適用在不同平台,而要再加上特定的 unit test framework (如 CppUnit) 時,只需簡易套用,可以直接 reusable 而不必重頭寫。

2014年7月17日 星期四

[SQL] 透過 INNER JOIN 更新 Table 新增的欄位數值 @ MySQL 5.6

對於一些當作收集 log 用途的 table tb_log ,隨著時間增加後,通常會再整理另一個 tb_status 的 table,快速查詢各個狀態,設計上就會定期批次從 tb_log 取出資料,存進 tb_status 中。

假設 tb_log 有 5 個欄位,一開始只覺得需要 2 個欄位的資訊,就把 tb_status 設定為 2 個欄位,然而過一陣子後,想多記錄一個欄位時,只好變動 tb_status ,但新增的欄位沒有舊資料,就變成要從 tb_log 取出來再存進 tb_status 了

碰到這種問題,有一個解法就是使用 INNER JOIN 來處理:
  1. 先從 tb_status 找出欄位未有值的資料
  2. 從 tb_log 組出 tb_status 所需的資料
  3. 透過  Update 指令更新
情況敘述:

mysql> describe tb_log;
+--------+--------------+------+-----+---------+----------------+
| Field  | Type         | Null | Key | Default | Extra          |
+--------+--------------+------+-----+---------+----------------+
| f0     | int(11)      | NO   | PRI | NULL    | auto_increment |
| f1     | varchar(8)   | YES  |     | NULL    |                |
| f2     | varchar(8)   | YES  |     | NULL    |                |
| f3     | varchar(8)   | YES  |     | NULL    |                |
| f4     | varchar(8)   | YES  |     | NULL    |                |
+--------+--------------+------+-----+---------+----------------+

mysql> describe tb_status;
+--------+--------------+------+-----+---------+----------------+
| Field  | Type         | Null | Key | Default | Extra          |
+--------+--------------+------+-----+---------+----------------+
| f1     | varchar(8)   | YES  | PRI | NULL    |                |
| f2     | varchar(8)   | YES  |     | NULL    |                |
| f3     | varchar(8)   | YES  |     | NULL    |                |
+--------+--------------+------+-----+---------+----------------+


其中 tb_status.f3 則是新建出來,未有資料的。

第一步:先找出 tb_status.f3 是空的(新進資料會有 f3 數值,只有舊資料沒有)

mysql> SELECT f1 WHERE f3 IS NULL;

第二步:從 tb_log 組出 f3 資料,由於 tb_log 是流水帳,且 tb_status 本身也可以從 tb_log 查詢出來的,只需組出 tb_status 需要的欄位即可:

mysql> SELECT f1, f3 FROM tb_log GROUP BY f1;

第三步,把上述兩個資料 JOIN 起來:

SELECT tb1.f1, tb2.f3 FROM
( SELECT f1 WHERE f3 IS NULL ) AS tb1, (SELECT f1, f3 FROM tb_log GROUP BY f1) AS tb2
WHERE tb1.f1 = tb2.f2;


最後,追加更新 tb_status 的用法:

UPDATE tb_status AS tb4
INNER JOIN

(
SELECT tb1.f1, tb2.f3 FROM
( SELECT f1 WHERE f3 IS NULL ) AS tb1,
(SELECT f1, f3 FROM tb_log GROUP BY f1) AS tb2
WHERE tb1.f1 = tb2.f2
) AS tb3

ON tb4.f1 = tb3.f1

SET

tb4.f3 = tb3.f3;

2014年7月15日 星期二

iOS 開發筆記 - 使用 CocoaPods 管理第三方函式庫

記得前幾年都用 git 跟 git submodule 管理,一切都是手動處理,包含 Xcode 添加 include path 等,現在則是想要把一些把玩的東西 open 出來,就想到用 CocoaPods 來管理,畢竟不是每個人都那麼勤勞的 XD

在此以 CocoaAsyncSocket 和 facebook-ios-sdk 為例。

首先,先安裝 CocoaPods:

$ sudo gem install cocoapods
$ pod update


接著搜尋想要的套件:

$ pod search CocoaAsyncSocket

-> CocoaAsyncSocket (7.3.5)
   Asynchronous socket networking library for Mac and iOS.
   pod 'CocoaAsyncSocket', '~> 7.3.5'
   - Homepage: https://github.com/robbiehanson/CocoaAsyncSocket
   - Source:   https://github.com/robbiehanson/CocoaAsyncSocket.git
   - Versions: 7.3.5, 7.3.4, 7.3.3, 7.3.2, 7.3.1, 7.2.2, 7.0.3, 0.0.1 [master repo]

$ pod search facebook-ios-sdk

-> Facebook-iOS-SDK (3.15.1)
   The iOS SDK provides Facebook Platform support for iOS apps.
   pod 'Facebook-iOS-SDK', '~> 3.15.1'
   - Homepage: https://developers.facebook.com/docs/ios/
   - Source:   https://github.com/facebook/facebook-ios-sdk.git
   - Versions: 3.15.1, 3.15.0, 3.14.1, 3.14.0, 3.13.1, 3.13.0, 3.12.0, 3.11.1, 3.11.0, 3.10.0, 3.9.0, 3.8.0, 3.7.1, 3.7.0, 3.6.0, 3.5.3, 3.5.2, 3.5.1,
   3.5.0, 3.2.1, 3.2.0, 3.1.1, 3.1.0, 3.0.8, 3.0.7, 3.0.6.b, 3.0.5.b, 1.2, 1.last, 0.0.1 [master repo]




接著,開始透過 Xcode 新增一個 Project (HelloPods) 後,接著改用 Terminal 在 Project 目錄下新增 Podfile

$ cd ~/path/ios-project
$ vim Podfile
pod 'CocoaAsyncSocket'
pod 'Facebook-iOS-SDK'
$ pod install
Analyzing dependencies
Downloading dependencies
Installing Bolts (1.1.0)
Installing CocoaAsyncSocket (7.3.5)
Installing Facebook-iOS-SDK (3.15.1)
Generating Pods project
Integrating client project

[!] From now on use `HelloPods.xcworkspace`.


接著,就改用 HelloPods.xcworkspace 吧!

$ open HelloPods.xcworkspace

現況雖然可以輕鬆添加新增的函式庫 header files 了,但在 import 時沒有 autocomplete 的功能,若很在意的話,可在 Project (HelloPods) -> Target (HelloPods) -> Build Settings -> User Header Search Paths -> 增加 ${SRCROOT} 以及 recursive 屬性。(添加完出錯時,在把它刪掉吧)

如此一來,工作應該就跟平常沒什麼兩樣啦。未來,可以一樣可以用 pod update / pod outdated 更新套件最新資訊。

另外,目前 Project (HelloPods) 目錄結構:

$ ls ~/path/ios-project
HelloPods HelloPods.xcworkspace Podfile Pods
HelloPods.xcodeproj HelloPodsTests Podfile.lock


對於哪些目錄需要用 git 管理,可以參考一下官網介紹:Should I ignore the Pods directory in source control?

以上則是利用 CocoaPods 及其管理的第三方函式庫的用法,至於不再 CocoaPods 管理的套件呢?除了在 Podfile 中描述來源外,其套件也要撰寫 *.podspec 描述,請參考 CocoaAsyncSocket / CocoaAsyncSocket.podspec;對於 Podfile 的部分,則是直接指定 git 位置即可:

$ vim Podfile
pod 'AFNetworking', :git => 'https://github.com/gowalla/AFNetworking.git'

2014年7月11日 星期五

[Javascript] 使用 AJAX/jQuery 與 JSONP/Callback 處理跨 domain 問題

很久沒寫 ajax 了 :P 由於機器整合不易?所以 API 就是要跨網域多台機器要互動。至於原理可以看看 wiki JSONP 簡介,在此筆記一下 Server Site 跟 Client Site 的用法

Server Site API 支援 JSONP 使用模式:

假設 API 輸出結果為 {"status":true} 的 JSON 格式,改成多一個 function name 來包裝: callbackFunc({"status":true})

例如:

$ wget -qO- http://example.com/api
{"status":true}

$ wget -qO- 'http://example.com/api?mycallback=HelloCallback'
HelloCallback({"status":true})


Client Site:

<script>
$(document).ready(function(){
$.ajax({
type: 'GET',
dataType: 'jsonp' ,
jsonpCallback: 'HelloCallback',
url: 'http://example.com/api',
data: 'mycallback=HelloCallback',
success: function(data) {
console.log(data);
}
});
});
</script>


若 Server Site 用的參數剛好是 callback 的話:

$ wget -qO- http://example.com/api
{"status":true}

$ wget -qO- 'http://example.com/api?callback=HelloCallback'
HelloCallback({"status":true})


那 Client Site 可以化簡成:

<script>
$(document).ready(function(){
$.ajax({
type: 'GET',
dataType: 'jsonp' ,
url: 'http://example.com/api',
success: function(data) {
console.log(data);
}
});
});
</script>


可以用 Chrome Browser 觀看 jQuery 發出的 Requests 長什麼樣子,主因是 jQuery 預先幫忙包好了。

2014年7月7日 星期一

[Linux] 使用 PCRE 進行字串取代 (C++/CPP/Regular Express/Replace) @ Ubuntu 14.04

安裝:

$ sudo apt-get install libpcre3-dev

程式碼:

#include <iostream>
#include <string>
#include <pcrecpp.h>

int main() {
        std::string s = "Hello 123-234-456 World";
        pcrecpp::RE("[0-9]+").GlobalReplace("", &s);
        std::cout << "Result: " << s <<"\n";
        return 0;
}


結果:

$ g++ -g t.cpp -lpcrecpp && ./a.out
Result: Hello -- World

2014年7月6日 星期日

iOS 開發筆記 - 列出系統內建 monospaced font (等寬字型/等寬字體)

列出系統所有字型:

for (int i=0 ; i< [[UIFont familyNames] count]; ++i) {
        NSLog(@"Font Family: %@", [UIFont familyNames][i]);
        for (int j=0; j<[[UIFont fontNamesForFamilyName:[UIFont familyNames][i]] count] ; ++j) {
            NSLog(@"Font Name: %@", [UIFont fontNamesForFamilyName:[UIFont familyNames][i]][j]);
        }
}


找尋 monospaced font:

NSString *words = @"Hello World!";
for (int i=0 ; i< [[UIFont familyNames] count]; ++i) {
    //NSLog(@"Font Family: %@", [UIFont familyNames][i]);
    for (int j=0; j<[[UIFont fontNamesForFamilyName:[UIFont familyNames][i]] count] ; ++j) {
        //NSLog(@"Font Name: %@", [UIFont fontNamesForFamilyName:[UIFont familyNames][i]][j]);
     
        float width = -1 ;
        bool found = true;
        for (int k=0; k< [words length]; ++k) {
            unichar word = [words characterAtIndex:k];
            CGSize wordSize = [[NSString stringWithFormat:@"%C", word] sizeWithAttributes:@{
                NSFontAttributeName: [UIFont fontWithName:[UIFont fontNamesForFamilyName:[UIFont familyNames][i]][j] size:16]
            }];
            if (width < 0) {
                width = wordSize.width;
            } else if (width != wordSize.width) {
                found = false;
                break;
            }
        }
        if (found) {
            NSLog(@"width(size16): %f, monospaced font: %@", width, [UIFont fontNamesForFamilyName:[UIFont familyNames][i]][j]);
        }
    }
}


結果:

width(size16): 9.601562, monospaced font: CourierNewPS-BoldMT
width(size16): 9.601562, monospaced font: CourierNewPS-ItalicMT
width(size16): 9.601562, monospaced font: CourierNewPSMT
width(size16): 9.601562, monospaced font: CourierNewPS-BoldItalicMT
width(size16): 9.601562, monospaced font: Courier-BoldOblique
width(size16): 9.601562, monospaced font: Courier
width(size16): 9.601562, monospaced font: Courier-Bold
width(size16): 9.601562, monospaced font: Courier-Oblique
width(size16): 9.632812, monospaced font: Menlo-Italic
width(size16): 9.632812, monospaced font: Menlo-Bold
width(size16): 9.632812, monospaced font: Menlo-Regular
width(size16): 9.632812, monospaced font: Menlo-BoldItalic

iOS 開發筆記 - 計算一個字的寬度

程式碼:     NSString *words = @"Hello World! 您好!";
    for( int i=0 ; i<[words length] ; ++i ) {
        unichar word = [words characterAtIndex:i];
        CGSize wordSize = [[NSString stringWithFormat:@"%C", word] sizeWithAttributes:nil];
        NSLog(@"Char: %C, Font Width: %f", word, wordSize.width);
    }


Char: H, Font Width: 8.666016
Char: e, Font Width: 6.673828
Char: l, Font Width: 2.666016
Char: l, Font Width: 2.666016
Char: o, Font Width: 6.673828
Char:  , Font Width: 3.333984
Char: W, Font Width: 11.326172
Char: o, Font Width: 6.673828
Char: r, Font Width: 3.996094
Char: l, Font Width: 2.666016
Char: d, Font Width: 6.673828
Char: !, Font Width: 3.333984
Char:  , Font Width: 3.333984
Char: 您, Font Width: 12.000000
Char: 好, Font Width: 12.000000
Char: !, Font Width: 12.000000


若要指定 font ,可以透過設定 Attributes:

@{
    NSFontAttributeName: [UIFont fontWithName:@"Courier" size:16]
}


指定 Courier 字型: Char: H, Font Width: 9.601562
Char: e, Font Width: 9.601562
Char: l, Font Width: 9.601562
Char: l, Font Width: 9.601562
Char: o, Font Width: 9.601562
Char: , Font Width: 9.601562
Char: W, Font Width: 9.601562
Char: o, Font Width: 9.601562
Char: r, Font Width: 9.601562
Char: l, Font Width: 9.601562
Char: d, Font Width: 9.601562
Char: !, Font Width: 9.601562
Char: , Font Width: 9.601562
Char: 您, Font Width: 16.000000
Char: 好, Font Width: 16.000000
Char: !, Font Width: 16.000000

2014年7月5日 星期六

[OSX] 移除 Avira Antivir 方式 @ Mac 10.9.3



在 Mac 用小紅傘一陣子了,但小紅傘一旦有新的軟體時,常常定時顯示出來,好不厭煩 XD 為了更新軟體,以前只要透過拖拉方式把小紅傘拖進垃圾桶即可移除,再裝新版即可,最近卻發現拖進垃圾桶沒反應,找了一下正確的反安裝流程:
  • 從 Finder -> Go -> Utilities -> Avira-Uninstall
  • 從 Spotlight -> 搜尋 Avira -> Avira-Uninstall
移除之後,就可以安裝新的 Avira Antivir 啦

2014年7月3日 星期四

iOS 開發筆記 - 使用 C++ Source Code

最近把玩練習時,想說用一下 CPP 的,寫完後發現要整進 Xcode 卻發現 compile error,例如在 AppDelete.m 中:

#include "Test.h"

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
 
    Test *obj = new Test();
 
    return YES;
}


其中 Test.h:

#ifndef __Test__Test__
#define __Test__Test__

#include <iostream>

class Test {
public:
    int x;
};


#endif /* defined(__Test__Test__) */


編譯時就會顯示找不到 iostream 等訊息,解法?早期大概有指定 Compile 參數,現在還滿容易的,只要把用到 CPP 的 Objective-C 檔案,從 .m 改成 .mm 即可。

以此就是將 AppDelete.m 更名為 AppDelete.mm ,就可以編譯成功,真是太方便了!

[Linux] 使用 wget 測試 Remote Resource Availability @ Ubuntu 14.04

wget 有 --spider 模式,挺方便的 :P

$ wget --spider http://www.google.com/favicon.ico
Spider mode enabled. Check if remote file exists.
Resolving www.google.com (www.google.com)... 173.194.33.148, 173.194.33.144, 173.194.33.145, ...
Connecting to www.google.com (www.google.com)|173.194.33.148|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [image/x-icon]
Remote file exists.

2014年7月2日 星期三

[Linux] 使用 ClamAV 掃毒 @ Ubuntu 14.04 Server

有一台Linode機器一直用來當跳板,若沒出事就不會進去逛,最近突然想到,不會不哪天被入侵還不知道?就先試看看最粗淺的用法,掃毒:

$ sudo apt-get install clamav
$ sudo freshclam
$ sudo clamscan -r /
...

----------- SCAN SUMMARY -----------
Known viruses: 3492457
Engine version: 0.98.1
Scanned directories: 13386
Scanned files: 50860
Infected files: 0
Total errors: 8826
Data scanned: 1996.67 MB
Data read: 19701.90 MB (ratio 0.10:1)
Time: 210.216 sec (3 m 30 s)

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/xvda        47G   20G   25G  44% /
none            4.0K     0  4.0K   0% /sys/fs/cgroup
devtmpfs        996M  4.0K  996M   1% /dev
none            200M  208K  200M   1% /run
none            5.0M     0  5.0M   0% /run/lock
none            997M     0  997M   0% /run/shm
none            100M     0  100M   0% /run/user


看起來耗時不多,有沒有用就是另一回事了 XDD 例如躲在記憶體內以及沒被偵測出來的...

2014年7月1日 星期二

[Python] SHA256 Tree Hash @ Ubuntu 14.04

來源:AWS Computing Checksums

最近在用 AWS Glacier 備份資料時,發現 AWS 回傳的 SHA Checksum 的數值跟系統內建的 sha25sum 不一致,而 AWS Computing Checksums 有提供 Java 跟 C# 版本,因為邏輯很簡單,就順手刻一個 Python 版本:changyy/aws-glacier-sha256-tree-hash


iOS 開發筆記 - 使用自定字型



用法:
  1. 將字形(MyCustomFontName.ttf)拖進Supporting Files,記得要勾選 "Add to tagets"

  2. 更新 *.plist 資訊,新增 Fonts provided by application,新增剛剛添加的字型檔名(含副檔名)
  3. 簡易使用 NSLog(@"MyCustomFont: %@", [UIFont fontWithName:@"MyCustomFontName" size:12]);  ,不需副檔名,若 NSLog 輸出不是 null 即可。