AFMultipartFormData使用JSON

AFNetworking處理上傳檔案時,用來組合request參數的AFMultipartFormData,除了能夾帶file,還可以包含其他request parameter,例如

AFHTTPRequestOperation *requestOperation = [manager POST:urlString parameters:nil constructingBodyWithBlock:^(id formData) {
        // 上傳圖片檔案
        UIImage *uploadImage =  [UIImage imageNamed:@"img_test.png"];
        NSData  *imageData = [CommonObject reduceImageSize:uploadImage];
        [formData appendPartWithFileData:imageData name:@"file" fileName:uploadFileName mimeType:@"text/plain"];
     
        // 也可以包含其他request parameter
[formData appendPartWithFormData:[@"Karen_Chang" dataUsingEncoding:NSUTF8StringEncoding]
                                    name:@"UserId"];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSLog(@"%@", responseObject);
 } failure:^(AFHTTPRequestOperation *operation , NSError *error) {
        NSLog(@"%@", error);
}];

若request parameter是NSArray,NSArray又由一堆JSON Object組成,該如何append formData???

直接將NSArrayNSData
NSData *inputArray1 = [NSKeyedArchiver archivedDataWithRootObject:origionalArray];
[formData appendPartWithFormData:inputArray1 name:@"ArrayInput"];
會拋出如下錯誤
Error Domain=com.alamofire.error.serialization.response Code=-1011 "Request failed: internal server error (500)" UserInfo={NSUnderlyingError=0x7ff24545fa50 {Error Domain=com.alamofire.error.serialization.response Code=-1016 "Request failed: unacceptable content-type: text/html" 

後來改寫法如下,就成功了^^
NSError *writeError = nil;
NSData *inputArray2 =
[NSJSONSerialization dataWithJSONObject:origionalArray
                                options:NSJSONWritingPrettyPrinted
                                  error:&writeError];
[formData appendPartWithFormData:inputArray2 name:@"ArrayInput"];

AVPlayerViewController保留PlaybackControls並加上自訂touch event

AVPlayerViewController的showsPlaybackControls屬性預設為YES
但如果自訂Gesture如下,會因為預設的單擊本來就賦予給隱藏/顯示PlaybackControls而無效
AVPlayerViewController *playerVC = [[AVPlayerViewController alloc]init];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(ChangeStatus)];
[tapGestureRecognizer setNumberOfTapsRequired:1];
[playerVC.view addGestureRecognizer:tapGestureRecognizer];

如果想要達到單擊影片,能依照當下播放與否決定是否加上PLAY的遮罩圖示(如下圖)
則可以改寫touchesBegan event如下
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if (self.playerVC.player.rate == 1.0) {
        // 播放中
    } else if (self.playerVC.player.rate == 0.0) {
        // 已暫停
    }
}


Android Parcel occurs 'Unmarshalling unknown type code'

Paracel一直以來都用得很順,即使是下列這種複雜的資料結構都OK
A class implements Parcelable {
    private B dataB;
    private ArrayList myList1;
    private ArrayList myList2;

    // 其他省略...

    // region Parcelable
    private A(Parcel parcel) {
        try {
            dataB= parcel.readParcelable(B.class.getClassLoader());
            myList1= parcel.readArrayList(String.class.getClassLoader());
            myList2= parcel.readArrayList(C.class.getClassLoader());
        } catch (Exception e) {
            Log.e("parcelable error", "A class:" + e.toString());
        }
    }

    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        public A createFromParcel(Parcel parcel) {
            return new A(parcel);
        }

        public A[] newArray(int size) {
            return new A[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeParcelable(dataB, i);
        parcel.writeList(myList1);
        parcel.writeList(myList2);
    }
    // endregion
}

B class implements Parcelable {
    private String id;
    private String name;
    // 其他省略...
}

C class implements Parcelable {
    private String gender;
    private String age;
    // 其他省略...
}

但最近用一樣的寫法,在A class中實作Parcel的時候,write部分沒問題read部分卻crash並出現錯誤java.lang.RuntimeException: Parcel android.os.Parcel@ffd1087: Unmarshalling unknown type code 7667825 at offset 1152

檢查了三款自訂的Parcel Class A、B、C都有確實實作相關methods,後來看到此篇文章Parcel Unmarshalling unknown type code提到資料的讀寫要按照相同順序,回頭檢查欄位高達四十個的B class才發現順序正確,但是write部分寫了三十六個欄位,read部分卻讀取了三十七個欄位,也因此影響了Parcel A的讀取部分@@

特此紀錄一下,在使用Parcel時要特別注意資料的讀寫順序與個數!!! 


Android設定圓角

如題
如果Button沒有要做到按下去有不同效果,懶得特地製作drawable xml給button吃,
那可以考慮如下做法,直接寫程式透過GradientDrawable達成

// 設定按鈕drawable

GradientDrawable shape =  new GradientDrawable();

shape.setCornerRadius(18);
  //圓角
shape.setColor(Color.parseColor("#FF7419"));  //顏色

Button sellButton = this.getActivity().findViewById(R.id.myButton);
sellButton.setBackground(shape);

iOS設定圓角

最常見設定圓角的方式就是變更屬性cornerRadius 
    self.yourView.layer.cornerRadius = 10;
但此法只能提供四個角都擁有相同弧度的效果

四個角的設定都不同,例如弧度尺寸、或有的要圓弧有的要直角,那就無法單靠cornerRadius 屬性了,以設定只有左上角+左下角有圓弧為例子,寫法如下
    UIBezierPath *maskPath = [UIBezierPath
                              bezierPathWithRoundedRect:self.yourView.bounds
                              byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerBottomLeft)
                              cornerRadii:CGSizeMake(10, 10)
                              ];
   
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.frame = self.bounds;
    maskLayer.path = maskPath.CGPath;
   
    self.yourView.layer.mask = maskLayer;

參考文章
how to set cornerRadius for only top-left and top-right corner of a UIView?

MAC環境使用Python上傳檔案至HDFS

單純記錄錯誤

from hdfs import Client
client = Client('http://yourIP:50070')
client.upload('遠端路徑', '本地端檔案')

建立HDFS連線OK
list HDFS中檔案列表也OK
但upload遭遇如下錯誤
NewConnectionError: < requests.packages.urllib3.connection.HTTPConnection object at 0x10df23a20>: Failed to establish a new connection: [Errno 8] nodename nor servname provided, or not known

原因在DNS解析不到
解法是設定本地端hosts

MAC設定hosts參考文章
手動設定網址與 IP 對應的 hosts 檔教學,適用 Windows、Mac OS X 與 Linux 系統
[教學]Mac OS X也能編輯與修改Hosts檔案方法

也遇到相同upload錯誤的參考文章
Python操作HDFS利器之hdfs

PS:HDFS操作所使用的python package是hdfs

Garbled output on ipython console

開發環境如下
MAC OS+Anaconda+Spyder(python 3.6.1)

之前iPython Console都好好的,可能最近升級Anaconda的關係?  部分error會輸出亂碼T_T
按照這篇how can I show all warnings in the iPython console in Spyder?升級qtconsole 也沒用

反正error跟SQL command有關,懶得再動整個Anaconda環境,就直接進Terminal用isql執行SQL command看詳細錯誤(−_−;) 
搞定XD

Can't open lib 'ODBC Driver 13 for SQL Server'

上一篇Install pyodbc on MAC 除了說明安裝方式,也提到無法import pyodbc時要怎麼解決
接下來就是寫程式

結果SQL連線馬上卡關,會遇到如下錯誤
('01000', "[01000] [unixODBC][Driver Manager]Can't open lib 'ODBC Driver 13 for SQL Server' : file not found (0) (SQLDriverConnect)")

弄了二天,剛安裝的driver或package都不知道亂裝了幾道  囧
在終端機下tsql指令也確實能連接資料庫並取回資料
tsql可以正常執行 isql卻不行嗚嗚

網路上大家說的freetds.conf、odbcinst.ini、odbc.ini也都設定了依舊無解(我懷疑是因為anconda的虛擬環境我不熟 >"<)
最終還是找到了勉強的解法,可以順利在Anconda下的Spyder繼續寫程式了
解法請參考文章PyODBC : can't open the driver even if it existsKaren Chang的回答

Install pyodbc on MAC

在MAC環境透過python連接SQL Server有二款知名的套件pyodbc、pymssql  微軟說明

安裝步驟如下
  • 打開終端機
  • 依序輸入這些指令
    brew update
    brew install unixodbc
    brew install freetds --with-unixodbc
    指令參考來源
    ps:brew update不一定要, 看個人
  • 繼續輸入conda指令 安裝pyodbc
    conda install -c anaconda pyodbc=4.0.16
    指令參考來源
  • 開啟IDE開始寫程式
    import pyodbc 確認是否安裝成功
  • 如果不成功會出現下列錯誤
    ImportError: dlopen(/Users/karenchang/anaconda/lib/python3.6/site-packages/pyodbc.cpython-36m-darwin.so, 2): Symbol not found: _PySlice_AdjustIndices
    Referenced from: /Users/karenchang/anaconda/lib/python3.6/site-packages/pyodbc.cpython-36m-darwin.so
    Expected in: flat namespace in /Users/karenchang/anaconda/lib/python3.6/site-packages/pyodbc.cpython-36m-darwin.so
  • 承上,解決方式為升級python至3.6.1以上
    像我本來是3.6.0 升級後就可以import pyodbc 了
    解決方式參考文章

'....failed to commit files' Couldn't communicate with a helper application.

如題,某天在Xcode commit files遇到此狀況,解決方式如下

●先確認你的git name & email是否跑掉(空值)
Show you the global settings for user.name and user.email (and other things)
git config --global -l
參考網址 https://forums.developer.apple.com/thread/18560

●如果真的跑掉,再設定回去吧
1.更改git電子郵件
指令    xcrun git config --global user.email xxxx
範例    xcrun git config --global user.email karen_chang@somedomain.com
2.更改git用戶名
指令    xcrun git config --global user.name xxxx
範例   xcrun git config --global user.name Karen_Chang

Core Data Fetches Instrument

最近參考Core Data by Tutorials v2.0調整library效能
在第九章Measuring and Boosting Performance Page224中提到demo程式碼Fetch Duration花費約2,000 microseconds(2 seconds)

我也測了一下自己的專案效能,發現不得了,取個二三筆也要2秒多(如下圖粉紅圈起處2,113)

改啊改 都已經在fetch data前先透過countForFetchRequest確認是否真的要向entity下查詢
怎麼還是2秒多 @_@

後來才發現是單位明明就是microseconds
電子書雞婆的換算2,000 microseconds(2 seconds)是錯的!!!
2,000 microseconds應該是0.002 seconds才對 囧


雖然有點囧,但至少改寫成可避免效能差的寫法 T_T

[Crashlytics] Warning: NSUncaughtExceptionHandler is 'uncaughtExceptionHandler' in another library

最近自行製作蒐集log/crash的library,想當然爾一定會override uncaughtExceptionHandler如下
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
void uncaughtExceptionHandler(NSException *exception) {
}

要停止報warning就只能二擇一,停用自製library或Crashlytics
不然就是繼續置之不理反正只是warning,但是否會有不可預期的狀況就難講了@_@

deprecation message for pip list

想說看一下電腦裡到底裝了多少Python package,在MAC環境下pip list遭遇如下警訊
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.

按照warning的說法,解決方式有二種
●第一種
創建pip.ini檔案,在檔案裡設定list時要使用的展示格式
[list]
format=columns

●第二種
每次下指令時多打這一串--format columns

Unable to export Apple Push Services certificate in .p12 format

前陣子憑證快到期重新製作了推撥用的p12
結果發現匯出格式無法選擇p12

原因是keychain的左側panel選擇了「所有項目」,而不是「我的憑證」(下圖紅2)。那為何會這樣???
因為我使用搜尋功能(下圖紅1)去快速查找憑證檔時,沒有先點選「我的憑證」,而keychain一打開預設是點選到「所有項目」...
爛透了XD

[Reprinted]如何在MAC環境正確安裝Python

MAC預設搭載Python,不過通常是早不被官方建議的2.X版

為了不動到系統預設Python,卻又要兼容2.X和3.X的Python(礙於有些元件還是得跑在2.X上),參考下列二篇文章即可順利安裝!!!
Installing Python on Mac OS X
Mac OSX 正確地同時安裝 Python 2.7 和 Python3

確認安裝是否成功的畫面










=====2017.3.10 Updated=====
今天為了使用Rodeo,安裝了Anaconda
結果Python也一併包在Anaconda一起安裝

原因在於環境變數的搜尋順序變這樣了



使用指令在MAC OS增加環境變數

最近在安裝Homebrew,為了日後方便使用,打算設定環境變數
但又跟MAC OS指令不熟,找了不少新舊文章,都無法成功設定變數,最後找到一篇確實有效且說明淺顯易懂的文章,分享給大家參考~連不會指令的我都可以一次成功XD

實際設定過程中,唯一一個不同的地方是,設定完畢最後再用echo "$PATH"確認時,要另開新的terminal視窗才能看到已經更新後的PATH

[iOS]Find SQLite file by simulator from Xcode 8

假設application執行時有儲存Core Data,想查看實際資料的步驟如下

STEP 1 安裝可查閱SQLite檔案的軟體
我是安裝Firefox的外掛SQLite Manager

STEP 2 查詢所用的simulator對應序號(下圖紅框處)

STEP 3 開啟Finder前往檔案位置
/Users/{yourMacLoginName}/Library/Developer/CoreSimulator/Devices/{simulator identifier}/data/Containers/Data/Application/{application identifier}/Documents/

yourMacLoginName:登入mac的使用者名稱
simulator identifier   :STEP 2的紅框處序號
application identifier:此序號代表你的application。直接參考此篇文章Task4即可取得SQLite位置,也不用單取此序號。
最懶的方式是直接前往
/Users/{yourMacLoginName}/Library/Developer/CoreSimulator/Devices/{simulator identifier}/data/Containers/Data/Application/,然後從Application資料夾找到修改日期最新的子資料夾(因為剛剛才執行完程式),進去Documents資料夾即可看到SQLite檔案。
又或者是你知道Core Data對應產生的SQLite檔案名稱,也可直接在Application資料夾搜尋嚕

STEP 4 開啟SQLite Manager查看資料

[iOS]Unable to load class named 'XXX' for entity 'XXX'

根據stackoverflow的討論,Unable to load class問題已於Xcode7修正,但不知為何還是遇到,特此紀錄一下

開發工具:Xcode 8.0
開發語言:Objective-C
遇到問題:CoreData: warning: Unable to load class named 'analytics.OpenLog' for entity 'OpenLog'.  Class not found, using default NSManagedObject instead.
解決方式:刪除Current Product Module

[iOS]Core Data Lightweight Migration

Data Model的任何變化(例如attribute renaming或add entity)基本上都要有個mapping的機制,core data才能依據不同model版本做升級,除非舊版資料你不要了,那就是直接砍掉建新的,也不用看本篇文章XD

Apple的Core Data提供貼心快速的Lightweight Migration,只要你的異動程度符合如下情況
  • Simple addition of a new attribute
  • Removal of an attribute
  • A non-optional attribute becoming optional
  • An optional attribute becoming non-optional, and defining a default value
  • Renaming an entity or property
都可以不用自己寫mapping,而是透過Apple的機制無痛升級!

簡單描述升級步驟
STEP 1 選取你要升級的Model(副檔名xcdatamodel)

STEP 2 從Menu選擇「Editor→Add Model Version...」
這邊是從第一版升(ClientLogModel.xcdatamodel)到第二版,所以是base on原始的ClientLogModel,然後替第二版取個帶有版本號的新名稱ClientLogModel 2

STEP 3 變更current model為ClientLogModel 2

STEP 4 開始編輯entity或attribute
除了修改entity結構,當初透過「Editor→Create NSManagedObject Subclass...」建立給entity套用的class也要自己做相對應的調整,又或者再次透過「Editor→Create NSManagedObject Subclass...」操作直接更新前一版的subclass

STEP 5 加上最關鍵的程式碼,要求啟動Lightweight Migration
NSError *error = nil;
NSURL *storeURL = <#The URL of a persistent store#>;
NSPersistentStoreCoordinator *psc = <#The coordinator#>;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
    [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
    [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
 [psc addPersistentStoreWithType:<#Store type#>
                    configuration:<#Configuration or nil#> URL:storeURL
                    options:options error:&error];

STEP 6 跑跑看你的程式,已經無痛升級不會再報下列錯誤
The model used to open the store is incompatible with the one used to create the store.

參考資料
Lightweight Migration from Apple Document
Core Data Migrations Tutorial: Lightweight Migrations


2017.01.20 補充  "修改Entity名稱"遭遇問題
今天把所有Entity都rename,想說舊的entities和相關的NSManagedObject Subclass都不用到,就刪光光,結果Lightweight Migration失敗@_@
後來才想到,舊版資料都刪掉,Lightweight Migration不知道新舊版的mapping,要如何幫我自動升級啦XD
再回頭把垃圾桶裡的舊class加回來,就好嚕>////<
上述作法若還是有問題,可以替你的NSManagedObjectContext加上MergePolicy設定值
 [[self managedObjectContext] performBlockAndWait:^{
     [[self managedObjectContext] setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];    
 }];
參考資料