2014年1月4日 星期六

SOLVE 2.23: Apps must follow the iOS Data Storage Guidelines or they will be rejected

某個 App 被 Apple Reject  了,因為是 2.23: Apps must follow the iOS Data Storage Guidelines or they will be rejected

其實這個問題好久以前就遇過了,也解過,但總是會忘記。

備份一下以免以後再發生同樣問題。

這個問題其實就是在 App 中有下載檔案的話要,每一個檔案都要備註是否要 Backup 至  iCloud。

解法也很簡單,把整個 Document 全部設定成 Do NOT Backup 就好了。

解法原帖:http://stackoverflow.com/questions/19964831/ios-flag-entire-document-directory-as-do-not-backup


簡單來說就是在你的  AppDelegate.m 的 didFinishLaunch 裡面加上

NSString *docsDir;
NSArray *dirPaths;
NSURL * finalURL;
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];


finalURL = [NSURL fileURLWithPath:docsDir];


[self addSkipBackupAttributeToItemAtURL:finalURL];
skip backup 如下

- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL

{

    assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);



    NSError *error = nil;

    BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]

                                  forKey: NSURLIsExcludedFromBackupKey error: &error];

    if(!success){

        NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);

    }

    return success;
}






2013年8月2日 星期五

Objective-C 關於 static

static 這個變數的修飾詞, 也許是比較沒緣份, 個人沒有太多的機會去實際使用到.

但是在 iOS 開發時使用 tableview 一定有看過下面的寫法.

static NSString *CellIdentifed = @"CellID";
....下略

static 在變數前表示將該變數宣告成 "靜態" 變數. 很多書或資料上都有介紹 static 的用法.

一直以來我也沒對他多做深入研究, 今日咱家技術長對 static 的一番解釋, 讓我對 static 有徹底了解的感覺.

要真正了解 static, 果然還是得從記憶體的角度去解釋.

EX:

static NSString *str = @"someStr";

表示系統會在記憶體中分配一塊固定的記憶體給 str 這個變數, 變數名稱必須是唯一.

 之後若重覆存取 str, 該變數在記憶體中的位置也不會改變.

什麼意思呢? 舉個簡單的例子好了

假設今天有兩個 method 如下

-(void) helloStatic {

      static int a=0;

      NSLog(@"static a = %d", a++);

}
與
-(void) noStatic {

      int a=0;

      NSLog(@" a = %d", a++);

}
然後在 viewDidLoad 呼叫這兩個 method, 如下
-(void)viewDidLoad {
      int i = 0;
      for(i=0;i<5;i++){
          [self helloStatic];
      }
      for(i=0;i<5;i++){

          [self noStatic];

      }
}
那麼印出來的結果會是
static a = 0
static a = 1
static a = 2
static a = 3
static a = 4
a = 0
a = 0
a = 0
a = 0
a = 0

Why???

在 viewDidLoad 中, 分別呼叫了五次 helloStatic 與 noStatic

helloStatic 中使用 static 修飾詞, 所以每次在對 a 這個變數做存取時, 系統都在同一個固定的記憶體位址做+1的動作, 所以印出來的結果會是 0 ~ 4.

但 noStatic 沒有使用 static, 所以每次呼叫 noStatic 這個方法時, 系統都會分配不同的記憶體位址給 a 且初始值為 0, 所以印出來5次的值皆為 0.

至於要怎麼在同一個檔案中誇  method 存取 static 變數? 只要使用 extern 就可以了.

一樣, 人腦 compiler 而己, 有錯誤請指正, 感謝各位

2013年7月9日 星期二

Asset framework 簡單範例

ALAssets 簡介 參考

An instance of ALAssetsLibrary provides access to the videos and photos that are under the control of the Photos application.

The library includes those that are in the Saved Photos album, those coming from iTunes, and those that were directly imported into the device. You use it to retrieve the list of all asset groups and to save images and videos into the Saved Photos album.

簡單說就是其實存在 camera roll 裡的東西不是單純的影片和照片,而是一個一個的 Asset 裡面包很了很多其他的資訊,像是 Location。

昨天有位朋友問我要如何實現 iPhone 內建相機左下角打開 camera roll 的功能,且button要是camera roll裡最後一張的圖片。

這個 Button 分成兩個部份,一個是要如何擷取 camera roll 裡最後一張照片出來,第二個是要開啟相簿(目前似乎無法像 Apple 直接打開某一個 Asset)

要做到這功能必須使用 AssetsLibrary.framework 且要有 block 的觀念。

請 下載 DemoCode

這個 DemoCode 很多都是由 Apple 官方文件修改而來的,有興趣更進一步了解的可以參考 這裡

還有一點要注意的是,在 Democode 裡的 ViewController.h 必須 adopt UIImagePickerControllerDelegate 和 UINavigationControllerDelegate 協定

其他說明在 democode 裡都有註解表示了,就不在此多加闡述了,有問題可以留言。


2013年7月5日 星期五

Objective-C property屬性


Property 是在Objective-C 2.0之後才有的,主要是方便取存或設定物件裡的實體變數, 系統會幫你寫好 setter 和 getter (在 xcode4.5 之後不需要搭配 @sythesize 這個合成指令, compiler會幫你關聯至在原變數名稱前加上底線 ex  _abc).


另外 Property 有幾個屬性可以設定, 語法如下

@property(attribute1, attribute2…)int iVal;

這些屬性分別為

readonly : 唯讀,(compiler 不會幫你合成 setter method)。

readwrite : 可讀可寫(Default)。

assign : 在設值時替換新舊資料(Default)。

retain : 在設值時retain新的資料,release舊資料。

copy : 在設值時copy一份新資料,release舊資料。

strong /weak : iOS5 之後取代 retain 和 copy, 由compiler幫你決定使用那一種.

nonatomic : non safety thread

atomic :  safety thread(Default)

assign 就只是單純的將新值賦予變數, 如下

- (void)setX:(int)iVal {
       _value = iVal;
}

retain用於物件。會在給值前先 release 舊的值並於新的物件上將其引用計數器+1 後賦予變數, 如下

- (void)setX:(NSString*)sVal {
          if (_string != sVal ) {
                [_string release];
                _string = [sVal retain];
        }
}


copy 與 retain 基本上是一樣的, retain 是將該物件的引用計數器+1, 而 copy 會依照類別型態做不同的動作, 這部份還待理解....但是一切在 iOS 5 提出 ARC 後, retain 和 copy 都被 strong 和 weak 取代了, 交由 compiler 幫你決定是用 retain 或是 copy.

strong 跟 retain 沒兩樣, 每次呼叫時都會將引用計數器+1, weak 則只是單純的參照記憶體位址.

weak 最常用的兩個地方是 delegation 中的 id<myDelegate> delegate 和 subviews.

因為所有的 subviews 都被 mainview 所持有, 所以只需參照記憶體位址即可.

atomic 表示若同時有多個地方呼叫該變數的 setter 或 getter 則必須一個一個慢慢來. 較安全, 但效能較低.

nonatomic 表示多人能同時呼叫 setter 和 getter 安全性較低,但性能較高, 大多數時間都設定為此屬性.

2013年7月4日 星期四

Objective-C Delegate 委派觀念及使用整理

## DemoCode 下載

delegation 是一種 design pattern (設計模式),方便在各個物件之間傳遞參數以及互相支援。

@protocol 是Obj-c 裡的一個關鍵字,直到@end關鍵字做結束,通常宣告在 .h 檔置於 @interface 之上,用法如下:

//aaa.h
@protocol myDelegation <NSObject>
   @required
         -(void)delegationPrint;
   @optional
         -(int)delegationLog:(int) iVal;
@end

@interface aaa <NSObject>

@property (weak, nonatomic) id <myDelegation> delegate;
-(void) someMethod;
-(void)anotherMethod:(NSString *)str
@end
在 aaa.h 宣告了一個叫做 myDelegation 的 protocol(協定) 出來並定義兩個方法 delegationPrint 以及 delegationLog:

其中 delegationPrint 屬於 @require,表示若有其他類別採納了 myDelegation 這個protocol則"一定"要實現 delegationPrint 方法,否則編譯器會出現警告,反之 @option 則可以不實現。

再來宣告並讓編譯器自動合成一個 "採納 myDelegation" 的泛型物件叫做 delegate,且屬性為 weak及nonatomic 其中 weak 為必要,避免物件相互持有無法釋放造成 memory leak。

**採納myDelegation的泛型物件名稱為增加閱讀性,一般都固定名稱為 delegate,不建議採取其他名稱。

**唯有採納 myDelegation 的物件可以呼叫 @protocol 所定義的方法(method)。

程式流程怎麼跑用 log 來看最清楚不過了,所以在每個 Method 都加了 NSLog 方便大家做 tracking.

假設今天有個類別 bbb 採納了 aaa 的 protocol 則程式片段應如下。


//aaa.m
-(void) someMethod {
         //確保 delegate 不為空且可回應 delegatePrint Method
     if(self.delegate != nil && [self.delegate delegatePrint]){
         NSLog(@"aaa.m someMethod exec");
         [self.delegate delegatePrint];
     }
}

-(void)anotherMethod:(NSString *)str {
     if(self.delegate != nil && [self.delegate delegateLog:]){
        NSLog(@"aaa.m anotherMethod exec");
        NSLog(@"str = %@ and return = %d",str, [self.delegate delegateLog:123]);
     } 
}


// bbb.h
#import "aaa.h"
@interface bbb:UIVierController <myDelegation>
@end



//bbb.m
@implementation ViewController
-(void)viewDidLoad {
      aaa *myAAA = [aaa new];
      myAAA.delegate = self;
      [myAAA someMethod];
      [myAAA anotherMethod:@"Hello World!"];
}
-(void)delegationPrint{     NSLog(@" bbb.m delegationPrint exec"); } -(int)delegationLog:(int) iVal{     NSLog(@"bbb.m delegationLog exec");     NSLog(@"bbb.m delegationLog: %d",iVal);     return iVal*10; }
@end
程式執行的結果輸出如下

aaa.m someMethod exec

bbb.m delegationPrint exec

aaa.m anotherMethod exec

bbb.m delegationLog exec

bbb.m delegationLog:123

str = HelloWorld! and return = 1230


以上程式碼片段是人腦 coding + 人腦 compiler,若是有錯誤的地方還請大家指正。




2013年6月25日 星期二

FMDB 隨手記



原址:
http://bonjouryentinglai.wordpress.com/2011/03/20/fmdb%EF%BC%9A%E6%88%91%E7%9A%84sqlite%E6%95%91%E6%98%9F/

         //需加入 imageIO.framework & MessageUI.framework

-開啟/關閉資料庫
使用資料庫的第一件事,就是建立一個資料庫。要注意的是,在iOS環境下,只有document directory 是可以進行讀寫的。在寫程式時用的那個Resource資料夾底下的東西都是read-only。因此,建立的資料庫要放在document 資料夾下。方法如下:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths objectAtIndex:0];
NSString *dbPath = [documentDirectory stringByAppendingPathComponent:@"MyDatabase.db"];
FMDatabase
*db = [FMDatabase databaseWithPath:dbPath] ;
if (![db open]) {
NSLog(@“Could not open db.”);
return ;
}
通常這段程式碼會放在UIViewController中viewDidLoad的函式裡。指定路徑後,用[FMDatabase databaseWithPath:]回傳一個FMDatabase物件,如果該路徑本來沒有檔案,會新增檔案,不然會開啟舊檔。最後呼叫[db open]可以開啟該資料庫檔案,[db close]則關閉該檔案。
-建立table
如果是新建的資料庫檔,一開始是沒有table的。建立table的方式很簡單:
[db executeUpdate:@"CREATE TABLE PersonList (Name text, Age integer, Sex integer, Phone text, Address text, Photo blob)"];
這是FMDB裡很常用的指令,[FMDatabase_object executeUpdate:]後面用NSString塞入SQLite語法,就解決了。因為這篇主要是在講FMDB,所以SQLite的語法就不多說了,上述程式碼建立了一個名為PersonList的table,裡面有姓名、年齡、性別、電話、地址和照片。(嗯….很範例的一個table)
-插入資料
插入資料跟前面一樣,用executeUpdate後面加語法就可以了。比較不同的是,因為插入的資料會跟Objective-C的變數有關,所以在string裡使用?號來代表這些變數。
[db executeUpdate:@"INSERT INTO PersonList (Name, Age, Sex, Phone, Address, Photo) VALUES (?,?,?,?,?,?)",
@"Jone", [NSNumber numberWithInt:20], [NSNumber numberWithInt:0], @“091234567”, @“Taiwan, R.O.C”, [NSData dataWithContentsOfFile: filepath]];
其中,在SQLite中的text對應到的是NSString,integer對應NSNumber,blob則是NSData。該做的轉換FMDB都做好了,只要了解SQLite語法,應該沒有什麼問題才是。
-更新資料
太簡單了,不想講,請看範例:
[db executeUpdate:@"UPDATE PersonList SET Age = ? WHERE Name = ?",[NSNumber numberWithInt:30],@“John”];
-取得資料
取得特定的資料,則需使用FMResultSet物件接收傳回的內容:
FMResultSet *rs = [db executeQuery:@"SELECT Name, Age, FROM PersonList"];
while ([rs next]) {
NSString *name = [rs stringForColumn:@"Name"];
int age = [rs intForColumn:@"Age"];
}
[rs close];
用[rs next]可以輪詢query回來的資料,每一次的next可以得到一個row裡對應的數值,並用[rs stringForColumn:]或[rs intForColumn:]等方法把值轉成Object-C的型態。取用完資料後則用[rs close]把結果關閉。
-快速取得資料
在有些時候,只會query某一個row裡特定的一個數值(比方只是要找John的年齡),FMDB提供了幾個比較簡便的方法。這些方法定義在FMDatabaseAdditions.h,如果要使用,記得先import進來。
//找地址
NSString *address = [db stringForQuery:@"SELECT Address FROM PersonList WHERE Name = ?",@"John”];
//找年齡
int age = [db intForQuery:@"SELECT Age FROM PersonList WHERE Name = ?",@"John”];
大概就是這樣囉~對於在Objective-C上使用SQLite有困難的朋友,看完之後是不是覺得一切都變的很簡單呢

2013年5月9日 星期四

Invalid binary (uniqueIdentifier)

這兩天送審新的 App 被 Apple 以我有呼叫非法的 API 而 rejected。

隨後又寄了封信告訴我是不可以使用 UDID,但我本身的 Code 早就已經全改成 UUID 囉。

所以應該是第三方的套件造成的問題。

有這個問題的捧友可以打開 terminal 切到專案目錄下執行 grep -Rnis 'uniqueIdentifier' *

這會列出所有包含 uniqueIdentifier 關鍵字的檔案。

以我的例子來說他會告訴我在 libadon.a 裡發現關鍵字,這是國內廣告商 VPon 的檔案。

我隨即去電詢問 Vpon,但他們一再跟我保證他們的 SDK 沒有問題,他們已經將相關的程式註解掉了。

但是問題還是沒解決,只好反覆再交叉測試,最後發現原來是 ADMOB 搞得鬼。

但令我耐悶的是 grep 並沒有回我 admob 裡有這個關鍵字,以致於我一開始方向就錯了。

把 Admob 的 SDK 從 6.2.1 更新到 6.4.1 就沒問題了。

但這裡要注意的是,若你把 6.2.1 檔案砍掉重新加入 6.4.1 還是沒解決問題的話。

請到 target -> build setting 搜尋 search paths ,再找到 library search paths 把裡面所有舊的 6.2.1 的路徑全都刪了,應該就可以了。

為了解這問題花了我一個早上呀。。。希望有幫到各位



參考網址

http://stackoverflow.com/questions/16409966/app-rejected-but-i-dont-use-udid

https://groups.google.com/forum/?fromgroups=#!topic/google-admob-ads-sdk/G7zIDyRTnJs