2014年12月31日 星期三

在iOS專案引入外部Library的另一種選擇 - git subtree

CocoaPods

CocoaPods可說是目前iOS coder之間最流行的module管理方式,CocoaPods非常適合那些自己很少去維護的外部library,通常都是等別人更新完之後自己下pod update更新。但是當我們也需要經常性的去維護這個library時,我們需要在另外一個git repository修改,然後在自己的專案下pod update指令更新,實在很麻煩!

Git Subtree

那麼對於需要經常去修改的pod,我們就可以採用Git Subtree來管理。使用Git Subtree來管理的好處是什麼呢?

  1. 原有的工作流程不會被打斷,只需要照平常一樣commit就好,更新library的遠端repository只要再打一行指令就好,要從遠端repository把更新拉下來一樣只需要一行指令。
  2. 新專案成員把repository clone下來之後不用再另外下指令更新sub tree,全部的東西都已經被clone下來了

參考:
https://medium.com/medium-eng/how-we-modularized-mediums-ios-codebase-8f8f26965c76
http://yutin.logdown.com/posts/188306-git-subtree-total-addendum-library
http://www.lemonlatte.tw/posts/175465-git-subtree-trial
http://www.xiwan.info/subtree-detach-subdirectory-into-separate-repository.html

2014年12月25日 星期四

Xcode 6已經內建類似Reveal的新功能

目前在Xcode裡查看view hierarchy有幾個比較知名的工具,像是RevealSpark
現在Xcode 6內建了一個很類似的功能,不過功能就沒有這麼完整。
inspector

定位按鈕左邊那顆就是了。

效果如下圖
inspector2

還可以看frame以及autolayout的constraint。
inspector3

其實這些基本功能就很夠用了,對我來說就可以把買Reveal的錢省下來了XD。
更詳細的介紹可以參考Xcode 6视图调试小贴士

2014年12月15日 星期一

在iOS App實作Airplay Audio功能

前言

由於Airplay開發者能控制的部分還不多,所以只能透過在自訂播放介面加入MPVolumeView的方式開啟Airplay的功能。

實作

如果需要包含內建的音量控制元件

MPVolumeView *volumeView = [ [MPVolumeView alloc] init] ;
[view addSubview:volumeView];

如果不需要

MPVolumeView *volumeView = [ [MPVolumeView alloc] init] ;
[volumeView setShowsVolumeSlider:NO];
[volumeView sizeToFit];
[view addSubview:volumeView];

在iOS裡聲音可以分為兩大類,App sound跟System sound,System sound做為按鍵回饋或是警示聲使用,剩下的就是App sound。在MPVolumeView選擇了Airplay裝置之後,系統會自動把App sound導到支援Airplay的裝置上。

在iOS7,MPVolumeView新增了兩個property

@property areWirelessRoutesAvailable;  
@property isWirelessRouteActive; 

以及兩個通知

NSString *const MPVolumeViewWirelessRoutesAvailableDidChangeNotification;  
NSString *const MPVolumeViewWirelessRouteActiveDidChangeNotification; 

可以偵測到有airplay以及使用者改變了airplay route,如果想要在偵測到可以使用Airplay裝置時加入動畫這會是一個好的實作方法。

2014年11月26日 星期三

iOS作業系統分佈情況

在開發App之前,通常會針對目前版本分布的狀況選擇要兼容到幾版,以前都不知道要去哪看,其實Apple自己就有提供這樣的資料。
https://developer.apple.com/support/appstore/
這是最近的分布情況,可以發現iOS7跟iOS8大概差不了太多。
iOS version pic

2014年11月13日 星期四

路跑相片自動辨識

跑馬的人喜歡看照片

最近趁著還沒上班前閒著無聊,就嘗試做看看之前一直很想做的路跑相片自動辨識。說實在自己也只參加過兩場路跑,但是跑完後想找到自己的照片可不是一件容易的事。雖然運動筆記相簿有提供號碼布搜尋,但通常是透過人工tag的方式,會需要一些時間去處理,沒有這麼即時。

自動化工具可以幹嘛?

雖然我不清楚運動筆記請小幫手tag相簿的流程,但是自動化工具基本上可以將一些號碼非常清楚且可辨識的相片自動標上號碼跟其他資料一起匯入資料庫內,如此一來將可以節省相當多的人力,小幫手只要去負責標出那些機器難以辨別的情況就好。

我還是不懂你在說什麼耶?

那來看個 demo影片好了

那到底什麼時候才能用?

因為我主攻不是影像處理,所以目前這支程式的辨識率還不是很高,也許等之後成熟一點再放出來吧。

2014年11月10日 星期一

Xcode 6.1拿掉class prefix

http://scottberrevoets.com/2014/07/25/objective-c-prefixes-a-thing-of-the-past/

從Xcode 6.1之後Apple拿掉了class prefix的選項,目前只有framework需要class prefix,剩下的app code以及新的swift都不需要。

2014年11月8日 星期六

iOS app development tools

Third-Party Library Management

CocoaPods

Crash Report

Crashlytics

Test framework

objective-c: Specta+Expecta、kiwi
swift: Quick

UI Test

FBSnapShots

Version Control

Gilt

Issue tracking

Redmine、Jira、Taiga

Continuous Integration

Jenkins via virtualbox
http://blog.hsatac.net/2014/06/install-osx-mavericks-on-virtualbox/

Server Deployment

Docker

Xcode Plugin Management

Alcatraz

Debug

Faux PasDeploymate

2014年7月8日 星期二

Admob以及Vpon聯播筆記

目標

利用Admob Mediation聯播admob廣告以及vpon廣告

步驟

申請vpon帳號

依照步驟申請即可

加入應用程式

vpon在新增應用程式的時候會詢問App上架後的URL,這個在iTunes Connect裡面新增App之後就可以看到了。
App URL location

申請admob帳號

依照步驟申請即可

加入應用程式

用手動加入應用程式,照步驟完成後選編輯中介服務->新增廣告聯播網->新增事件,接著就會出現這個畫面。
新增vpon聯播
Vpon的wiki裡有對這裡應該填什麼值做說明。
Label可以隨意,Parameter就是你在vpon新增的廣告id。
特別注意Class Name就是Admob Mediation SDK會去呼叫的class名稱,所以VPON admob mediation adapter的class name要跟這裡一樣,不然Admob Mediation SDK會找不到這個class。我遇到的情況是文件上面class名稱是VpadnAdmobCustomAd,但sample裡面是VponAdmobCustomAd。

整合SDK進project

使用cocoapods方式,編輯Podfile,加入兩個pod。

pod "Google-Mobile-Ads-SDK"
pod 'AdMobMediationAdapterVponSDK', :podspec => 'https://raw2.github.com/dearhui/AdMobMediationAdapterVponSDK/master/AdMobMediationAdapterVponSDK.podspec'

這樣就可以不用手動設定一堆麻煩的東西了。

使用SDK注意事項

其實聯播的運作方式就是使用同一個GADBannerView顯示不同廣告商發送的廣告內容。

admob

只要依照google developer的說明下去使用應該沒有什麼大問題,重點就是把GADBannerView顯示出來而已。
如果要讓admob顯示測試廣告,在GADRequest的testDevices加入GAD_SIMULATOR_ID。
記得GADBannerView的adUnitID要填入中介服務的id

- (void)setupAd
{
    // 在螢幕上方建立標準大小的視圖,
    // 可用的 AdSize 常值已在 GADAdSize.h 中解釋。
    self.bannerView = [[GADBannerView alloc] initWithAdSize:kGADAdSizeBanner];

    // 指定廣告單元編號。
    _bannerView.adUnitID = @"Your pub ID";

    // 通知執行階段,將使用者帶往廣告到達網頁後,該恢復哪一個 UIViewController,
    // 並將其加入檢視階層中。
    _bannerView.rootViewController = self;
}
- (void)loadAD 
{
    GADRequest *request = [GADRequest request];
    request.testDevices = @[
        GAD_SIMULATOR_ID
    ];
    // 啟動一般請求,隨著廣告一起載入。
    [_bannerView loadRequest:request];
}

vpon

vpon的廣告會在同一個GADBannerView顯示廣告內容,而且admob mediation SDK會自動呼叫vpon的class,只要admob中介服務有設定好,其他就不用操心。
目前加了AdMobMediationAdapterVponSDK這個pod之後會有 @synthesize of ‘weak’ property is only allowed in ARC or GC mode的問題,所以要在VponAdmobCustomAd.h加一行

@property(nonatomic, assign) id<GADCustomEventBannerDelegate> delegate;

應該就可以編譯成功了。記得pod update之後會自己加進去的東西會被清掉,需重加。

如果要讓vpon顯示測試廣告,需要加入設備的ID,如果沒加在log裡會提示你。
在VponAdmobCustomAd.m裡面

-(NSArray*)getTestIdentifiers
{
    return [NSArray arrayWithObjects:
            // add your test Id
            @"",
            nil];
}

目前vpon SDK的64 bit版本還在beta,所以如果不想編出64bit版本的binary,就要到Target->Build setting->Architecture裡面把arm64去掉。

2014年6月20日 星期五

Go HTML Parser

用Go寫爬蟲目前有兩個third party library還滿好用的。

  1. gokogiri: https://github.com/moovweb/gokogiri
  2. goquery: https://github.com/PuerkitoBio/goquery

gokogiri使用cgo封裝了libxml2,所以需要先安裝libxml2,環境架設比較麻煩,弄好之後可以用XPath的方式取element。而goquery基於Go的net/html以及cascadia,讓我們可以使用類似Jquery Selector的方式選取element。

Selector使用方式可以參考http://api.jquery.com/category/selectors/

搭配goquery的Readme變換一下selector應該就可以寫出一些基本的爬蟲了。

2014年6月6日 星期五

Mac OSX 安裝Go開發環境(Vim)

安裝Go

https://code.google.com/p/go/wiki/Downloads?tm=2下載go1.2.2.darwin-amd64-osx10.8.pkg照步驟安裝。

設定GOPATH

vim ~/.bash_profile

加入

export GOPATH=/Users/vampirewalk/develop/go
export PATH=$GOPATH/bin:$PATH

使用Vundle管理Vim plugin

參照 https://github.com/gmarik/Vundle.vim

安裝vim-go

在.vimrc裏面加入

Plugin 'fatih/vim-go'

然後在vim裏面:PluginInstall

設定colorscheme

curl -o ~/.vim/colors https://raw.githubusercontent.com/fatih/molokai/master/colors/molokai.vim
vim ~/.vimrc

在vimrc中,在” Put your non-Plugin stuff after this line之後加入

syntax on        
colorscheme molokai
set t_Co=256

autocmd BufNewFile,BufReadPost *.go set filetype=go

安裝YouCompleteMe

https://github.com/Valloric/YouCompleteMe

調整MacVim的字型

vim ~/.gvimrc

寫入

set guifont=Monaco:h16

完成囉,開始開發吧

2014年5月7日 星期三

撰寫Cinnamon applet

什麼是Applet?

就是在panel上一格一格的東西,可以讓你快速執行某些設定或功能。
applet

如何製作一個簡單的Applet?

請參考How To Make A Cinnamon Applet (Force Quit Applet Tutorial

API要到哪裡查?

http://www.roojs.com/seed/gir-1.2-gtk-3.0/gjs/

2014年2月7日 星期五

調整UIButton的Image大小

SetBackgroundImage

當給定一個固定大小的UIButton時,如果圖的大小跟UIButton不同,一種解法是

UIImage *image = [UIImage imageNamed:@"buttonImage"];
UIEdgeInsets edgeInsets = UIEdgeInsetsMake(30.0f, 25.0f, 29.0f, 9.0f);
UIImage *resizeableImage = [image resizableImageWithCapInsets:edgeInsets];
[backButton setBackgroundImage:resizeableImage forState:UIControlStateNormal];

SetImage

如果背景圖已經用掉了,也就是UIButton現在已經有背景圖,但是前景還想再放一個跟按鈕大小不同的圖,這時可以利用UIButton裡的UIImageView。Stackoverflow上有教一個方法是

[button setContentMode:UIViewContentModeScaleAspectFit];

但是這個只能放大圖不能縮小圖。

另一種方法是透過設定imageEdgeInsets可以縮小Image

[button setImage:[UIImage imageNamed:@"buttonImage"] forState:UIControlStateNormal];
button.imageEdgeInsets = UIEdgeInsetsMake(10,20,10,20);

效果大概像這樣
original
Modified
圖片來自http://stackoverflow.com/questions/1957317/how-do-i-scale-a-uibuttons-imageview, Hitesh Savaliya的答案

2014年2月6日 星期四

將NavigationBar從iOS6過渡到iOS7

首先需要判斷目前runtime是iOS6或是iOS7,使用下列macro

#define SYSTEM_VERSION_EQUAL_TO(v)                  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v)              ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v)                 ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v)     ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)

在iOS6裡面NavigationBar的顏色是用tintColor去設定

self.navigationController.navigationBar.tintColor = [UIColor whiteColor]

但是在iOS7裡面設定tintColor會把BackButton蓋掉,所以需要判斷version決定使用tintColor或是barTintColor。

if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) {
        self.navigationController.navigationBar.barTintColor = [UIColor greenColor];
}
else {
    self.navigationController.navigationBar.tintColor = [UIColor greenColor];
}

如果預設BackButton的顏色與目前tintColor不相配,需要改變顏色,但是又不想把全部的BarButtonItem樣式改掉時,可以用下面這樣的code。第一行會判斷UIBarButtonItem是否在UINavigationBar裡面,是才改變樣式。第二行是修改箭頭的顏色。

//set back button color
        [[UIBarButtonItem appearanceWhenContainedIn:[UINavigationBar class], nil] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIColor whiteColor], NSForegroundColorAttributeName,nil] forState:UIControlStateNormal];
//set back button arrow color
[self.navigationController.navigationBar setTintColor:[UIColor whiteColor]];

2014年1月4日 星期六

Mac注音輸入法選擇

之前用過Yahoo輸入法以及小麥輸入法,Yahoo輸入法缺點是切換速度太慢,小麥輸入法缺點是選字不夠聰明。
最近發現香草輸入法框架加上新酷音套件還滿好用的,不管是選字或是切換速度都讓我很滿意,所以來紀錄一下安裝過程。特別注意香草1.0版之後不支援新酷音套件,所以要用0.9版。
載點http://www.ptt.cc/bbs/MAC/M.1370684378.A.EC8.html
安裝方法請參考http://j9l.blogspot.tw/2012/09/in-on-mac.html

2014年1月3日 星期五

Cocoa concurrent programming 讀書筆記

Concurrent Programming: APIs and Challenges

原文: #2 Concurrent Programming
大陸網友的譯文http://blog.jobbole.com/52647/

Concurreny指的是同時執行許多事務,可以是在單一核心的CPU上利用不同時間片段(time slice)執行,或是將事務分配到多核心CPU上執行。

在OS X and iOS上的Concurrency API從底層到高層依序有

  1. pthread
  2. NSThread
  3. Grand Central Dispatch
  4. NSOperationQueue

另外還有一個比較不同的NSRunloop。

高層API是從低層封裝而來的,由於Concurrent程式碼非常複雜且難以管理,所以在撰寫程式時應該優先考慮高層API而非低層API。

Thread是Process的子單元,所有高層API都是從封裝thread得來的。你無法控制你的thread何時何地被列入排程,也無法決定會被執行多久。

使用thread最大的問題就是你必須自行管理thread,如果自己的程式跟底層framework都產生了大量的thread,這會被吃光memory與kernel資源。

Grand Central Dispatch

Grand Central Dispatch (GCD) 在OS X 10.6 and iOS 4被導入。GCD是從thread封裝而來,提供開發者高階的觀點來達成多工。GCD提供了幾個優先權不一的佇列(Queue),開發者只要將程式碼片段放入佇列中,GCD就會依據目前資源使用的狀況,幫你決定這些工作要送到自身管理的thread pool中的某條thread去執行。GCD提供了五個不同的queue:

  1. Main queue: 最終會在main thread上執行
  2. High priority queue: 背景執行,優先權高
  3. Default priority queue: 背景執行,優先權中等
  4. Low priority queue: 背景執行,優先權低
  5. Background priority queue: 背景執行,優先權最低

更詳細的資料可以看 http://blog.csdn.net/mobanchengshuang/article/details/10839049

雖然不同優先權的工作使用不同的queue聽起來很合理,但是強烈建議大多數情況下還是用 default priority queue 就好。
原因是,如果這些task存取共享資源,低優先權task有可能會block高優先權的task,造成priority inversion,app會卡住。

Operation Queues

Operation Queues是對GCD的Cocoa封裝,在一般情況下是最好最安全的選擇。Operation Queues有兩種queue

  1. Main queue: 最終會在main thread上執行
  2. Custom queue: 在背景執行

Task會以NSOperation子類別的型態在queue中被處理。當建構自己的NSOperation子類別時,可以選擇override Main方法或是start方法,前者實作上較為簡易,系統會自動幫你處理isExecuting以及isFinished 等狀態;後者則是可讓你自行管理這些狀態,在執行asynchronous task時應該使用這個方法。特別注意到狀態屬性必須是KVO-compliant的,如果不使用default accessor method改變這些狀態,記得要手動發出KVO Message(willChange… , didChange…)。

@implementation YourOperation
    - (void)main
    {
        // do your work here ...
    } 
@end
@implementation YourOperation
    - (void)start
    {
        self.isExecuting = YES;
        self.isFinished = NO;
        // start your work, which calls finished once it's done ...
    }

    - (void)finished
    {
        self.isExecuting = NO;
        self.isFinished = YES;
    }
@end

要讓自訂的NSOperation支援取消,應該經常檢查isCancelled狀態。

- (void)main
{
    while (notDone && !self.isCancelled) {
        // do your processing
    }
}

Operation queue提供一些使用GCD很難作正確的功能

  1. 控制被執行的operation個數
  2. 依據operation之間的相依性順序執行
[intermediateOperation addDependency:operation1];
[intermediateOperation addDependency:operation2];
[finishedOperation addDependency:intermediateOperation];

使用Operarion queue相較於GCD會有效能上的損失,但是在大多數情況下是微不足道的,應該盡量選擇使用Operarion queue來實作。

Run Loops

runloop

關於runloop的中文資料可以參考iOS多线程编程Part 1/3 - NSThread & Run Loop,講的非常詳細。
Runloop就是一個處理事件的loop,通常會綁定一個event source,如果沒有綁定source的話runloop會馬上結束。Event source分兩大類:

  1. Input sources
  2. Timer sources

當event source有新的事件時,runloop會被喚醒來處理事件,Timer sources的事件處理完後runloop不會結束,而Input sources事件處理完後runloop會結束。runloop自身有很多模式,可以想像成狀態,一次只能運行在一種模式下,在綁定event source時必須告訴runloop在哪些模式下要處理這個event source的事件。一個經典的情況就是,在Main Runloop的NSDefaultRunLoopMode綁定自訂的Timer source,如果使用者開始scroll的話,runloop會切換到UITrackingRunLoopMode模式,就不處理先前綁定的Timer source裡的事件了。解法就是讓這個Timer source裡的事件在多個模式下都可以被處理,那麼一開始綁定的時候就要設定NSRunLoopCommonModes模式,這是個模式集合,包含NSDefaultRunLoopMode、NSTaskDeathCheckMode、UITrackingRunLoopMode,只要runloop處於這三個模式其中之一,就會處理Timer source的事件。

一個runloop會綁定一個thread,main thread預設已經有main runloop運行。

Challenges of Concurrent Programming

撰寫concurrent program是極其複雜的,不同的task有可能會互相影響,進而導致未預期的結果。就連NASA這麼嚴謹的組織也會犯錯

Sharing of Resources

在thread之間共享資源是許多concurrency問題的邪惡根源。
如果有兩條thread同時對某個整數property做increment,原值為17。兩條Thread「同時」讀出值,並在memory中加1,thread A先寫回結果18,thread B再寫回結果18,property最終結果為18,而非想像中的19,這就是race condition。
另外在實際情況中,一行程式碼會被編譯器解譯成好幾行的機器碼,包含讀取與寫入。而編譯器為了優化速度,有可能不會照順序放置這些機器碼,這也是我們必須列入考量的地方。

Mutual Exclusion

Mutual exclusive access就是保證一次只有一條thread存取資源。Cocoa提供了一些Synchronization Tool:

  1. Atomic Operations
  2. Memory Barriers and Volatile Variables
  3. Locks
  4. Conditions
  5. Perform Selector Routines

使用這些tool是有代價的,效能會受到影響,應該盡量設計結構讓程式不需要Synchronization。
用了lock之後經常會發生一次只有一條thread在做事的情況,使用CPU strategy view觀察執行的情況再做調整。

Dead Locks

Dead Lock就是thread獲取不到資源無法執行下去而卡死,有可能是兩條thread互相等待對方完成,也有可能是自己lock住但是重複嘗試獲取lock導致自己deadlock。

Starvation

高優先權的thread一直佔住資源不讓低優先權的thread做事,低優先權的thread就挨餓了。在Reader-Writer問題中如果使用單純的讀寫鎖,萬一Reader一直源源不絕進來佔住鎖,Writer就會Starvation。

可改用Write-preferring或RCU(Read-Copy Update)解決。

Write-preferring是當目前有writer在等待時,新的Reader就不能獲取鎖,直到writer寫入完畢才能再繼續。

RCU(Read-Copy Update)就是讀-拷貝修改。被RCU保護的資料結構Reader不需要鎖即可訪問。Writer先拷貝一份副本,並在副本上修改數據。修改完後,向垃圾回收器註冊一個callback,垃圾回收器等待所有正在讀取數據的Reader發送已不再讀取數據的訊號,全部接收到之後再調用callback將指向原數據的指標改成指向修改過後的數據。

Priority Inversion

如果有一高優先權task與一低優先權task共享資源,而低優先權task先佔住了資源,照理來說低優先權task應該儘快結束工作好讓高優先權task可以拿到資源做事,結果好死不死有中優先權task一直進來插隊做事,讓低優先權task遲遲無法釋放lock,高優先權task拿不到想要的資源,這就是Priority Inversion。

Jserv也有介紹NASA’s Pathfinder事件

Conclusion

Concurrent programming真的很複雜。
Concurrency model應該越簡單越好。
安全作法是在Main thread把資料拉到Background Operation queue做事,做完再放回Main queue。