2014年4月29日 星期二

Core Data 概念



The NSFetchedResultsController is a type of controller provided by the Core Data framework that helps manage results from queries. 
NSFetchedResultsController幫我們管理我們要做的查詢
NSManagedObjectContext is a handle to the application’s persistent store that provides a context, or environment, in which the managed objects can exist. 
NSManagedObjectContext 幫我們產生一個存放managed objects的環境,並幫我們處理與persistent store之間的溝通。

//Sample code

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:
self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Master"]; 
aFetchedResultsController.delegate = self; 
self.fetchedResultsController = aFetchedResultsController; 
我們來了解一下cacheName這個參數的用法:
如果你不想要cache查詢結果,那直接傳nil給他就好。
如果你傳了cacheName給它,那代表它會在下次搜尋之前,先去檢查上一次的cache有沒有相同的值。
如果有找到相同的查詢,就不進行查詢直接取用它;如果不相同,就刪除上次的cache,然後存放這次的查詢結果供下次使用。


the persistent store 通常在操作 SQLite database
而The managed object model 是persistent store的抽象邏輯表現層。


Calling the getter for the delegate’s managedObjectContext starts a chain reaction in which
-(NSManagedObjectContext *)managedObjectContext, calls -(NSPersistentStoreCoordinator *)persistentStoreCoordinator and then in turns calls -(NSManagedObjectModel *)managedObjectModel. The call to managedObjectContext therefore initializes the entire Core Data stack and readies Core Data for use. 

//Delete時,有四個規則
  1. No action 什麼都不做:Does nothing and lets the related objects think the parent object still exists.
  2. Nullify 把每個相關連的物件的父物件設為nil:For each related object, sets the parent object property to null. 
  3. Cascade 把所有相關的物件也一併刪除:Deletes each related object.
  4. Deny 如果仍有相關物件,則禁止delete的動作:Prevents the parent object from being deleted if there is at least one related object. 

//NSFetchedResultsController是用來給UITableView使用的
You use a fetched results controller to efficiently manage the results returned from a Core Data fetch request to provide data for a UITableView object.

//讀取資料
- (NSArray *) readDataWithEntityName:(NSString *)entityName andPredicate:(NSPredicate *)predicate sortDescriptor:(NSString *)sortDescriptorString asceding:(BOOL)isAsceding{
    
    NSManagedObjectContext *context = [_coreData managedObjectContext];
    NSEntityDescription *entityDescription = [NSEntityDescription entityForName:entityName inManagedObjectContext:context];
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity:entityDescription];
    
    // Set example predicate and sort orderings...
    [request setPredicate:predicate];
    
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
                                        initWithKey:sortDescriptorString ascending:isAsceding];
    [request setSortDescriptors:@[sortDescriptor]];
    
    NSError *error;
    NSArray *array = [context executeFetchRequest:request error:&error];
    if (array == nil)
    {
        // Deal with error...
    }
    return array;

}

#pragma mark Save
- (void)saveContext
{
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
            // Replace this implementation with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}

#pragma mark Add
- (void) addNewRecordIntoEntityByName:(NSString *)entityName newObjects:(NSArray *)objects{
    
    NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:_managedObjectContext];
    
    // If appropriate, configure the new managed object.
    // Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
    for (NSDictionary *objectInfo in objects) {
        [objectInfo enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
            [newManagedObject setValue:obj forKey:key];
        }];
    }
    
    
    // Save the context.
    NSError *error = nil;
    if (![_managedObjectContext save:&error]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
}
#pragma mark Delete
//刪除指定物件
-(void) deleteRecord:(NSManagedObject *)managedObject{
    
    [_managedObjectContext deleteObject:managedObject];
    
    NSError *error = nil;
    if (![_managedObjectContext save:&error]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
}

//恢復上一步
-(void) undoRecord{
    
    [_managedObjectContext.undoManager undo];
    
    NSError *error = nil;
    if (![_managedObjectContext save:&error]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
}

//恢復下一步
-(void) redoRecord{
    
    [_managedObjectContext.undoManager redo];
    
    NSError *error = nil;
    if (![_managedObjectContext save:&error]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

}

2014年4月28日 星期一

imageNamed V.S. imageWithContentsOfFile

imageNamed

  • 說明

會先檢查cache中有沒有圖片,有的話直接取用,沒有的話,就把圖片放入cache中不釋放。

  • 範例

UIImage *image = [UIImage imageNamed:@"imageName.png"];


imageWithContentsOfFile

  • 說明

不檢查cache中有沒有圖片,每次都直接讀取圖片檔。

  • 範例

NSString *imageName = [NSString stringWithString:@"imageName.png"];
[UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] bundlePath], imageName]];

2014年4月27日 星期日

指標與記憶體(Pointers and Memory)

有四種指標變數,對應三種記憶體

一、Static/Global
  1. Static:作用範圍:被宣告的function中。生命週期:等同於這支應用程式。
  2. Global:作用範圍:所有的function都可存取。生命週期:等同於這支應用程式。
二、Automatic(Local)
作用範圍:被宣告的function中。生命週期:在這個function被呼叫的時候。
三、Dynamic
作用範圍:由指向這塊heap的指標所決定。生命週期:直到這塊memory被釋放。 

//關於Static的作用範圍,Objective-C跟C有些不同,請參考下列資料
Variable storage class specifiers are used when declaring a variable to give the compiler information about how a variable is likely to be used and accessed within the program being compiled. So far in this chapter we have actually already looked at two storage class specifiers in the form of extern and static. A full list of variable storage class specifiers supported by Objective-C is as follows:
  • extern - Specifies that the variable name is referencing a global variable specified in a different source file to the current file.
  • static - Specifies that the variable is to be accessible only within the scope of the current source file.
  • auto - The default value for variable declarations. Specifies the variable is to be local or global depending on where the declaration is made within the code. Since this is the default setting this specifier is rarely, if ever, used.
  • const - Declares a variable as being read-only. In other words, specifies that once the variable has been assigned a value, that value will not be subsequently changed.
  • volatile - Specifies that the value assigned to a variable will be changed in subsequent code. The default behavior for variable declarations.

2014年4月24日 星期四

NSArray排序(sorting)

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:@"recordDate" ascending:YES];
        NSArray *sortedArray = [queryArray sortedArrayUsingDescriptors:@[sortDescriptor]];

這裡要注意SortDescriptors是陣列,他會以第0個item為主,如果遇到二筆以上同順序的,才會以之後的item來排列。

如何在CoreData中做SUM運算

//節錄自http://stackoverflow.com/questions/8680975/iphone-core-data-how-to-get-sum-of-values-from-db
There are two ways to solve this problem:
first - get your data to NSSet or NSArray and use @sum operator:
//assume that `pens` are NSArray of Pen
NSNumber *countSum=[pens valueForKeyPath:@"@sum.count"];
second is using specific fetch for specific value with added NSExpressionDescription with a sum. This way is harder but better for larger db's


//以下為使用magicrecords
NSNumber *countSum = [RecordEntity MR_aggregateOperation:@"sum:" onAttribute:@"steps" withPredicate:nil];

NSLog(@"算總和 : %@",countSum);

nil & NULL

節錄自http://nshipster.com/nil/
SymbolValueMeaning
NULL(void *)0literal null value for C pointers
nil(id)0literal null value for Objective-C objects
Nil(Class)0literal null value for Objective-C classes
NSNull[NSNull null]singleton object used to represent null

TI-CC2541 PARAMETERS



android
max:39
min:39
latency:0
timeout:700

iOS
max:24
min:24
latency:0
timeout:72

//iOS限制如下,且只接受在connection時設定,不接受動態調整。
 底下是我們目前量測到的結果:

目前Firmware參數
調整後參數
Android參數
Connection Interval
80
80
39
Slave Latency
0
10
0
Supervision Timeout
2000
700
700
平均耗電流
232 uA
18 uA
496 uA
耗電量 (a X 30S)/3600
0.00193 mAh
0.00015 mAh
0.00413 mAh

https://developer.apple.com/hardwaredrivers/BluetoothDesignGuidelines.pdf
3.6 Connection Parameters
The accessory is responsible for the connection parameters used for the Low Energy connection. The accessory should request connection parameters appropriate for its use case by sending an L2CAP Connection Parameter Update Request at the appropriate time. See the Bluetooth 4.0 specification, Volume 3, Part A, Section 4.20 for details. The connection parameter request may be rejected if it does not comply with all of these rules:
Interval Max * (Slave Latency + 1) 2 seconds Interval Min 20 ms
Interval Min + 20 ms
Interval Max Slave Latency 4 connSupervisionTimeout 6 seconds
Interval Max * (Slave Latency + 1) * 3 < connSupervisionTimeout
If Bluetooth Low Energy HID is one of the connected services of an accessory, connection interval down to 11.25 ms may be accepted by the Apple product.
The Apple product will not read or use the parameters in the Peripheral Preferred Connection Parameters characteristic. See the Bluetooth 4.0 specification, Volume 3, Part C, Section 12.5. 

2014年4月23日 星期三

scope with continue command in objective c

因為聽聞有的語言若使用break會直接跳出最外層的迴圈,特別做此測試
+ (void) checkContinueCommandInLoop{
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 10; j++) {
            if (j == 5) {
                continue;
                //break;
            }
            NSLog(@"j : %i", j);
        }
        NSLog(@"i : %i", i);
    }

}

在objective-C中的結果為:scope只限於當前(最內層)的迴圈
例如
continue:
i會由0~9會跑完
j則會只跑0,1,2,3,4,6,7,8,9

break:
i會由0~9會跑完
j則會只跑0,1,2,3,4

補充:for-in迴圈也適用continue與break
for (NSNumber *number in tempArray) {
        if ([number isEqualToNumber:@5]) {
            continue;
        }
        NSLog(@"number : %@",number);

    }

2014年4月22日 星期二

Thread安全

 Thread之間存取變數的時間差可能導致變數存取的錯誤

- (void)setMyVar:(id)inMyVar { 
 [myVar release];
#warmming 這裡會有時間差 
 myVar = [inMyVar retain]; 
}

可以用Lock去鎖定,或是用下列的寫法來保證線程安全
- (void)setMyVar:(id)inMyVar { 
id tmp = myVar; 
myVar = [inMyVar retain]; 
[tmp release]; 
}

initWithFormat與stringWithFormat的區別

在MRC時有差別,在ARC時兩者沒差。
 [NSString stringWithFormat:...] 等同於[[[NSString alloc] initWithFormat:...] autorelease]

另外,[NSString new];等同於[[NSString alloc] init];

2014年4月21日 星期一

如果要coredata以同步的方式查詢,則要使用performBlockAndWait

__block NSArray *results = nil;
    [context performBlockAndWait:^{

        NSError *error = nil;
        
        results = [context executeFetchRequest:request error:&error];
        
        if (results == nil) 
        {
            [MagicalRecord handleErrors:error];
        }


    }];

NSUserDefaults不能塞CoreData的Entity

[[NSUserDefaults standardUserDefaults] setObject:enableDevices forKey:@"enableDevices"];

如果這裡的enableDevices是Entity,則編譯不會過

ARC(Automatic Reference Counting)

ARC(Automatic Reference Counting),不是在 runtime 回收記憶體,而是在編譯程式的時候,自動幫你加上與記憶體釋放有關的程式碼。

2014年4月20日 星期日

void *

一般來說,void常用在代表沒有回傳值的method上。
然而用來變數上時,代表不指定型別的指標變數,用來當作變數型別轉換之間的過渡,非常有用。

(void*)NULL);注意 NULL 不是空指標,但 NULL 轉型成指標後一定是空指標。

在 C 語言標準裡面 char 和位元組幾乎是同義詞。如果某實作決定讓 char 佔了 128 個位元,代表虛擬機器上的位元組就是 128 個位元。除了特例 bit field 外,char 是最終記憶體單位,其大小(sizeof)永遠是 1 不會改變。

2014年4月19日 星期六

Block的宣告備忘

轉錄自http://fuckingblocksyntax.com

As a property:

@property (nonatomic, copy) returnType (^blockName)(parameterTypes);

As a method parameter:

- (void)someMethodThatTakesABlock:(returnType (^)(parameterTypes))blockName;

As an argument to a method call:

[someObject someMethodThatTakesABlock: ^returnType (parameters) {...}];

As a typedef:

typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};

2014年4月17日 星期四

copy & mutableCopy

copy適用於一般情況
當你有mutable的變數時,一定要用mutableCopy,不然他會當成沒有mutableCopy的還給你。
NSArray *sample = @[@1,@2,@3];
NSMutableArray *mutableArray = [sample mutableCopy];
NSArray *array = [mutableArray copy];

有限狀態機

  1. 定義明確的狀態
  2. 定義明確的狀態轉換條件
  3. 輸入
  4. 輪出

2014年4月15日 星期二

Java & JavaScript & Objective-C 的i++與 ++i迴圈都是一樣的

用三種語言跑下列的迴圈都出現一樣的結果
+(void) plusPlusI {
    for (int i = 0; i<10; i++) {
        NSLog(@"i++ : %i",i);
    }
    for (int i = 0; i<10; ++i) {
        NSLog(@"++i : %i",i);
    }
    for (int i = 10; i>=0; i--) {
        NSLog(@"i-- : %i",i);
    }
    for (int i = 10; i>=0; --i) {
        NSLog(@"--i : %i",i);
    }

}


2014-04-15 18:28:16.572 ConceptComfirm[38257:60b] i++ : 0
2014-04-15 18:28:16.573 ConceptComfirm[38257:60b] i++ : 1
2014-04-15 18:28:16.573 ConceptComfirm[38257:60b] i++ : 2
2014-04-15 18:28:16.573 ConceptComfirm[38257:60b] i++ : 3
2014-04-15 18:28:16.573 ConceptComfirm[38257:60b] i++ : 4
2014-04-15 18:28:16.574 ConceptComfirm[38257:60b] i++ : 5
2014-04-15 18:28:16.574 ConceptComfirm[38257:60b] i++ : 6
2014-04-15 18:28:16.574 ConceptComfirm[38257:60b] i++ : 7
2014-04-15 18:28:16.574 ConceptComfirm[38257:60b] i++ : 8
2014-04-15 18:28:16.574 ConceptComfirm[38257:60b] i++ : 9
2014-04-15 18:28:16.575 ConceptComfirm[38257:60b] ++i : 0
2014-04-15 18:28:16.575 ConceptComfirm[38257:60b] ++i : 1
2014-04-15 18:28:16.575 ConceptComfirm[38257:60b] ++i : 2
2014-04-15 18:28:16.575 ConceptComfirm[38257:60b] ++i : 3
2014-04-15 18:28:16.575 ConceptComfirm[38257:60b] ++i : 4
2014-04-15 18:28:16.576 ConceptComfirm[38257:60b] ++i : 5
2014-04-15 18:28:16.576 ConceptComfirm[38257:60b] ++i : 6
2014-04-15 18:28:16.576 ConceptComfirm[38257:60b] ++i : 7
2014-04-15 18:28:16.576 ConceptComfirm[38257:60b] ++i : 8
2014-04-15 18:28:16.579 ConceptComfirm[38257:60b] ++i : 9
2014-04-15 18:28:16.580 ConceptComfirm[38257:60b] i-- : 10
2014-04-15 18:28:16.580 ConceptComfirm[38257:60b] i-- : 9
2014-04-15 18:28:16.580 ConceptComfirm[38257:60b] i-- : 8
2014-04-15 18:28:16.580 ConceptComfirm[38257:60b] i-- : 7
2014-04-15 18:28:16.581 ConceptComfirm[38257:60b] i-- : 6
2014-04-15 18:28:16.581 ConceptComfirm[38257:60b] i-- : 5
2014-04-15 18:28:16.581 ConceptComfirm[38257:60b] i-- : 4
2014-04-15 18:28:16.581 ConceptComfirm[38257:60b] i-- : 3
2014-04-15 18:28:16.581 ConceptComfirm[38257:60b] i-- : 2
2014-04-15 18:28:16.582 ConceptComfirm[38257:60b] i-- : 1
2014-04-15 18:28:16.582 ConceptComfirm[38257:60b] i-- : 0
2014-04-15 18:28:16.582 ConceptComfirm[38257:60b] --i : 10
2014-04-15 18:28:16.582 ConceptComfirm[38257:60b] --i : 9
2014-04-15 18:28:16.582 ConceptComfirm[38257:60b] --i : 8
2014-04-15 18:28:16.582 ConceptComfirm[38257:60b] --i : 7
2014-04-15 18:28:16.582 ConceptComfirm[38257:60b] --i : 6
2014-04-15 18:28:16.583 ConceptComfirm[38257:60b] --i : 5
2014-04-15 18:28:16.583 ConceptComfirm[38257:60b] --i : 4
2014-04-15 18:28:16.583 ConceptComfirm[38257:60b] --i : 3
2014-04-15 18:28:16.583 ConceptComfirm[38257:60b] --i : 2
2014-04-15 18:28:16.584 ConceptComfirm[38257:60b] --i : 1
2014-04-15 18:28:16.584 ConceptComfirm[38257:60b] --i : 0

libsqlite3.dylib v.s. libsqlite3.0.dylib

簡單的說,libsqlite3.0.dylib是真實的sqlite版號,而libsqlite3.dylib是一個捷徑Symbolic Link),它會自動指向目前最新版的sqlite

2014年4月14日 星期一

無限大

//math.h
INFINITY

設定UIDatePicker格式

//要靠Locale來改變格式
//AM/PM
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; 
[datePicker setLocale:locale];

//24時制
把地區改為Russian

2014年4月11日 星期五

pop到某一個特定的view

AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]
NSMutableArray *allViewControllers = [NSMutableArray arrayWithArray:[appDelegate.navigationController viewControllers]];
for (UIViewController *aViewController in allViewControllers) {
    if ([aViewController isKindOfClass:[RequiredViewController class]]) {
        [self.navigationController popToViewController:aViewController animated:NO];
    }
}

Category 與 Extension

Category
@interface NSString (Private)
@end
@implementation NSString (Private)
@end
不可用來添加實體變數
用來覆寫原有的方法
用來添加新的方法
寫在下面的Category才能用寫在上面Category的method

Extension
@interface NSString ()
@end
@implementation NSString
@end 
可以添加實體變數(所以原來在.h宣告的變數,都建議拿過來,降子.h才看不到私有變數)
用來覆寫原有的方法
用來添加新的方法

2014年4月10日 星期四

多國語系取用方式

新增一個Strings File,其中加入你要的多國語言key-Value對應

預設 Localizable.strings 取用方式           : NSLocalizedString(@“Hello", @"你好啊!”);
自己取名 MyLocalizable.strings 取用方式 : NSLocalizedStringFromTable(@“Hello", @"MyLocalizable”, @"你好啊!” );

反轉NSArray

NSArray* reversedArray = [[NSArray alloc] initWithArray:[[sourceArray reverseObjectEnumerator] allObjects]];

NSInterger就等於 int (在32位元系統時) 或 long (在64位元系統時)

#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;

#endif

2014年4月9日 星期三

iOS取用權限限制

如果使用下列幾項,就會在上架時被reject

  1. 打開/關閉wifi
  2. 取用bluetooth的m.a.c.地址
  3. 取用iDevice UDID

2014年4月7日 星期一

簡化AppDelegate的取用方式

AppDelegate.h
+ (instancetype)sharedDelegate;

AppDelegate.m
+ (instancetype)sharedDelegate
{
    return [UIApplication sharedApplication].delegate;

}

2014年4月6日 星期日

ScrollView content attribution 捲到底才執行



所以我們可以在ScrollViewDelegate中降子判斷

#pragma --mark UIScrollViewDelegate
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    float bottomOffset = scrollView.contentSize.height - (scrollView.bounds.size.height -scrollView.contentInset.bottom);
    NSLog(@"bottom(%f) = scrollView.contentSize.height(%f) - (scrollView.bounds.size.height(%f) -scrollView.contentInset.bottom(%f))",bottomOffset,scrollView.contentSize.height, scrollView.bounds.size.height, scrollView.contentInset.bottom);
    //捲到底才執行
    if ( scrollView.contentOffset.y == bottomOffset )
    {
        //TODO:...
    }
}

2014年4月2日 星期三

好用的第三方

  • SDWebImage
  • AFNetworking
  • MBProgressHUD
  • 大陸收集的 http://idevchina.com/t/20
  • http://kmlframework.com 是一個常用的地圖資料格式
  • https://github.com/vsouza/awesome-ios 全面性的收集
  • GPUImage
  • TPKeyboardAvoiding
  • https://github.com/ckteebe/CustomBadge 客製化小紅點
  • https://github.com/romaonthego/RETableViewManager 簡單加入各式的tableViewCell
  • https://www.cocoacontrols.com code4app抄襲的原型
  • https://github.com/jverkoey/nimbus 之前three20的作者跳出來開的
  • https://github.com/ccgus/fmdb SQLite的wrapper
  • https://github.com/facebook/pop Facebook的parse動畫效果開源
  • https://github.com/ReactiveCocoa/ReactiveCocoa iOS的響應式與函數式Framework
  • https://github.com/jpsim/PeerKit 整合Multipeer Connectivity Framework的應用
  • https://github.com/jpsim/CardsAgainst 使用peerKit的開源遊戲
  • http://openweathermap.org/api 全球氣象的open api
  • https://github.com/nathankot/NKMultipeer 結合了Multipeer Connectivity與RxSwift框架
  • http://sqlitebrowser.org 可以直接看sqlite的開源軟體
  • https://github.com/HarveyHu/BLEHelper 對藍牙BLE的好用framework
  • https://github.com/tiimgreen/github-cheat-sheet/blob/master/README.zh-cn.md GitHub秘籍
  • 測試平台
    • TestFlight
    • http://fir.im/
    • https://www.pgyer.com/
    • http://pre.im/
    • http://www.mtestin.com/
    • http://www.testfairy.com/
    • http://www.ineice.com/
  • https://www.diawi.com/ <=簡單、好用、免費