A. 請教庫沖突的導致的 plicate symbol 鏈接錯誤
場景:
解決TencentOpenAPI.framework與ZbarSDK中 _base64_encode 函數的沖突
後來在網路上搜尋,刪除掉 Other Linker Flag 的 -all_load 就可以解決靜態庫沖突的問題,
但是這樣做的話,會使一些外部的靜態庫,使用objc擴展函數(catagory)的方法失效。例如BaiMapApi
如果是有些庫使用到了擴展函數(catagory)可以分別對這個庫進行載入
使用:-force_load
-force_load BaiMapApi/libs/Release-iphoneos/libmapapi.a
(BaiMapApi是添加到當前目錄下的)
或
-force_load $(BUILT_PRODUCTS_DIR)/libxxx.a
(這里是直接添加靜態庫項目源碼的做法)
使用-force_load分別進行載入還是蠻方便的,如果有些函數加入了main函數使用all_load就相當麻煩了。
以上可以解決TencentOpenAPI.framework與ZbarSDK的沖突
如果兩個靜態庫沖突的結構是相同的,可以考慮將兩個靜態庫拆分出來進行合並。
查看文件的架構有哪些
$ lipo -info libzbar.a
Architectures in the fat file: libzbar.a are: armv7 (cputype (12) cpusubtype (11)) i386
將armv7解壓出來
lipo libzbar.a -thin armv7 -output libzbar-armv7.a
新建立一個文件夾出來存放解壓的(.o)文件
$ mkdir armv7
$ cd armv7
將靜態庫中的文件解壓
$ ar -x ../libzbar-armv7.a
然後將另一個靜態庫根據以上的步驟做一遍,然後觀察連個解壓的靜態庫中,有那些是一樣的就合並在一起,不過注意的是兩個靜態庫沖突的(.o)文件必須一致,否則也會出現錯誤。
合並完後進行打包了
$ libtool -static -o ../libnew-armv7.a *.o
如果像在虛擬機也使用,進行相同的步驟後,將i386的架構合並再一起就可以了。
合並靜態庫
$ lipo -create -output lib.a libnew-armv76.a libi386.a
plicate symbol是一種常見的鏈接錯誤,不像編譯錯誤那樣可以直接定位到問題的所在。但是經過一段時間的總結,發現這種錯誤總是有一些規律可以找的。
例如,我們有如下的最簡單的兩個類代碼:
// ClassA.h
#import <Foundation/Foundation.h>
@interface ClassA : NSObject
@end
// ClassA.m
#import "ClassA.h"
@implementation ClassA
@end
// ClassB.h
#import <Foundation/Foundation.h>
@interface ClassB : NSObject
@end
// ClassB.m
#import "ClassB.h」
@implementation ClassB
@end
編譯後出現的錯誤信息如下:
plicate symbol _OBJC_METACLASS_$_ClassA in:
/Users/dajie/Library/Developer/Xcode/DerivedData/linkTest-cpjaaatiyqpvxcbzfzpklcbqrgqg/Build/Intermediates/linkTest.build/Debug-iphonesimulator/linkTest.build/Objects-normal/i386/ClassA.o
/Users/dajie/Library/Developer/Xcode/DerivedData/linkTest-cpjaaatiyqpvxcbzfzpklcbqrgqg/Build/Intermediates/linkTest.build/Debug-iphonesimulator/linkTest.build/Objects-normal/i386/ClassB.o
plicate
symbol _OBJC_CLASS_$_ClassA in:
/Users/dajie/Library/Developer/Xcode/DerivedData/linkTest-cpjaaatiyqpvxcbzfzpklcbqrgqg/Build/Intermediates/linkTest.build/Debug-iphonesimulator/linkTest.build/Objects-normal/i386/ClassA.o
/Users/dajie/Library/Developer/Xcode/DerivedData/linkTest-cpjaaatiyqpvxcbzfzpklcbqrgqg/Build/Intermediates/linkTest.build/Debug-iphonesimulator/linkTest.build/Objects-normal/i386/ClassB.old:
2
plicate symbols for architecture i386clang:
error: linker command failed with exit code 1 (use -v to see invocation)
從上面出現問題的地方,我們應該能推測出是ClassA這個類出了問題。如果這個類是我們自己寫的,就容易辦一些。
可以考慮以下原因:
1.引入頭文件時,由於疏忽,誤引入.m文件。這種一般仔細檢查一下出現問題的類的源文件就能發現。
例:ClassB.m 文件修改成下面這樣
#import "ClassB.h>"
#import "ClassA.m」 // 這句話有問題
@implementation ClassB
@end
2.同一個類,實現兩次,即有兩個@implementation 。這種一般會有一個警告,也比較容易發現。
例:ClassB.m文件修改成下面這樣:
// ClassB.m
#import "ClassB.h」
@implementation ClassB
@end
@implementation ClassA
@end
3.工程文件,同一個類文件被引入了兩次,引起這種錯誤的原因大概有兩種:一是多人協作開發時,導致project文件合並沖突;二是同名文件不在同一目錄下,添加到工程時造成重復添加。 這種一般在文件視圖,用名字過濾器檢查一下就發現了。
4.
Targets的Build Phase設置項里,查看Complie
Sources這一項,看看出現問題的類是不是有重復的,用文件名過濾也比較容易發現。這種問題一般也是多人協作開發時,project文件沖突導致的。
發現這種問題,只要刪除就可以了。不過在我解決問題過程中,刪除其中一個文件時,重復文件會自動全部刪除,所以還需要單獨添加一下。
5.如果我們的工程中引用了第三方的庫,而恰好第三方的庫裡面有一個ClassA,也會出現這種問題。如果不能修改第三方的庫代碼,只能修改我信自己的代碼了。
B. ZBarSDK怎麼可以快速讀取和解碼
ps:在調試過程中正好蘋果發布了iOS7,於是也從Xcode 4升級了Xcode最新版,同樣真機設備也從6.1.3升級到7.0,看看會不會有兼容問題,總體來說沒有問題
下面介紹一下如何在iOS設備上使用二維碼
1.掃描二維碼(包括讀取和解碼)
掃描二維碼的開源庫有很多如 ZBar、ZXing等。博主先嘗試了ZXing,豎屏的情況下ZXing沒有問題,但是似乎橫屏會出現錯位的情況。於是博主放棄ZXing,使用ZBar。
這里要提一句,ZXing的開源做的比ZBar好,讀取和解碼速度也快,但是整合到自己的工程中是見痛苦的事情,不光是導入整個Demo工程,設置也相對繁瑣,如果不是真的需要超高性能應用的開發者,博主還是建議使用ZBar。
總的流程如下:
①下載ZbarSDK,包含ZbarSDK頭文件包、libzbar.a靜態鏈接庫文件以及資源文件三部分;
②在新工程中導入以下框架:AVFoundation.framework、CoreMedia.framework、CoreVideo.framework、QuartzCore.framework、libiconv.dylib(已存在則不需要重新加入);
③將ZbarSDK加入工程;
④檢查libzbar.a鏈接庫是否正常,如果沒有自動鏈接,需要手動加入,如下圖:
⑤在需要使用的頁面.h文件中引用頭文件#import "ZBarSDK.h"
⑥在需要使用的頁面.h中繼承 <ZBarReaderDelegate>協議
⑦在.m中實現協議的方法- (void) imagePickerController: (UIImagePickerController*) reader didFinishPickingMediaWithInfo: (NSDictionary*) info
[cpp] view plain
id<NSFastEnumeration> results = [info objectForKey: ZBarReaderControllerResults];
ZBarSymbol *symbol = nil;
for(symbol in results)
break;
[self dismissViewControllerAnimated:YES
completion:^{
}];
NSString *code = [NSString stringWithString:symbol.data];
⑧實現開始掃描的按鈕功能
[cpp] view plain
ZBarReaderViewController *reader = [ZBarReaderViewController new];
reader.readerDelegate = self;
reader.supportedOrientationsMask = ZBarOrientationMaskAll;
ZBarImageScanner *scanner = reader.scanner;
[scanner setSymbology: ZBAR_I25
config: ZBAR_CFG_ENABLE
to: 0];
[self presentViewController:reader
animated:YES
completion:^{
}];
2.生成二維碼
在生成二維碼的庫中QREncoder最為常見,但是由於中文字元的特殊性,生成中文的時候會出現一定的錯誤,所以博主改用libqrencode,是一個純C編寫的類庫,支持面也更廣泛。
①下載libqrencode源碼;
②在新工程中導入以下框架:AVFoundation.framework、CoreMedia.framework、CoreVideo.framework、QuartzCore.framework、libiconv.dylib(已存在則不需要重新加入);
③將libqrencode源碼加入工程;
④需要使用的頁面.m文件中引用頭文件#import "QRCodeGenerator.h";
⑤實現生成二維碼圖片的方法
[cpp] view plain
UIImage *image = [QRCodeGenerator qrImageForString:self.codeString imageSize:self.imageView_Code.bounds.size.width];
3.自定義掃描頁
ZXing支持自己創建掃描頁面,而由於ZBar使用的.a的鏈接庫文件,所以需要修改他生成的掃描頁,不能直接新建一個。
在上文中掃描二維碼第⑧項,對reader進行一些修改,代碼如下:
[cpp] view plain
for (UIView *temp in [reader.view subviews]) {
for (UIToolbar *toolbar in [temp subviews]) {
if ([toolbar isKindOfClass:[UIToolbar class]])
{
UIBarButtonItem *item1 = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(dismissOverlayView:)];
UIBarButtonItem *item2 = [[UIBarButtonItem alloc]initWithBarButtonSystemItem: target:nil action:nil];
UIBarButtonItem *item3 = [[UIBarButtonItem alloc]initWithTitle:@"相冊" style:UIBarButtonItemStyleBordered target:self action:@selector(clickPhotoToolBarItem:)];
NSArray *array = [NSArray arrayWithObjects:item1,item2,item3,nil];
[toolbar setItems:array animated:YES];
}
}
}
上述代碼中,取消按鈕和相冊按鈕方法等需要自己定義。方法中詳細代碼如下:
[cpp] view plain
//取消按鈕方法
- (void)dismissOverlayView:(id)sender{
[self dismissViewControllerAnimated:YES
completion:^{
}];
}
// 選取相冊
-(void)clickPhotoToolBarItem:(id)sender
{
if ([UIImagePickerController isSourceTypeAvailable:])
{
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
imagePicker.allowsEditing = NO;
imagePicker.sourceType = ;
[self dismissViewControllerAnimated:NO completion:^{}];
[self presentViewController:imagePicker animated:YES completion:^{}];
}
}