dyld: lazy symbol binding failed: Symbol not found

最近針對安全性升級調整程式碼,並重新釋出自製的framework套件,結果安裝此套件的專案build得過,但遇到dyld: lazy symbol binding failed: Symbol not found錯誤無法run

最後參考此文章dyld: lazy symbol binding failed: Symbol not found - Swift Kingfisher

雖然不是因為使用Swift Kingfisher造成,但情況大同小異,最終在使用套件的專案之Podfile加上如下語法,並且主動執行一次pod install即可。這段語法用途在於把所有Pod專案的build setting中的Build Libraries for Distribution一次性的設定為YES。要手動設定也是可以。

post_install do |installer|

  installer.pods_project.targets.each do |target|

    target.build_configurations.each do |config|

      config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES'

    end

  end

end

如果對語法有興趣,可以參考此文瞭解一下CocoaPods Hooks

Alamofire request在dispatchPrecondition發生錯誤

 實際發生錯誤的程式碼斷在Alamofire的Session.swift如下片段

extension Session: SessionStateProvider {

    func request(for task: URLSessionTask) -> Request? {

        dispatchPrecondition(condition: .onQueue(rootQueue)) // -->錯在此行

        return requestTaskMap[task]

    }

// 略

}

會出現此錯誤是因為App本身用了AF.request之外,又導入另一framework套件,而該套件也有使用Alamofire,使用的方式是自行建立一個新的Session如下,該套件透過自建的Session將AF.request替換成myAF.request執行各項API操作。

public let myAF: Session = {

    let configuration = URLSessionConfiguration.default

    configuration.requestCachePolicy = .reloadIgnoringCacheData

    configuration.tlsMinimumSupportedProtocol = .dtlsProtocol12

    let delegate = Session.default.delegate

    let manager = Session.init(configuration: configuration,

                               delegate: delegate,

                               startRequestsImmediately: true,

                               cachedResponseHandler: nil)

    return manager

}()


如果App本身未使用AF.request,只使用本套件,是沒有問題。但是當App有使用AF.request又導入本套件,就會因為多個執行緒造成錯誤,主要原因是因為Session的設計如下。

open class Session {

    /// Shared singleton instance used by all `AF.request` APIs. Cannot be modified.

    public static let `default` = Session()

}


因此,如果想要自訂configuration,又不想發生錯誤,套件內的configuration設定需要改成如下寫法

AF.session.configuration.requestCachePolicy = .reloadIgnoringCacheData

AF.session.configuration.tlsMinimumSupportedProtocol = .dtlsProtocol12

改完後,維持使用AF.request即可。

Swagger: Unable to render this defition

最近用.NET Core開發API專案,本來順順好用的Swagger突然無法提供服務








後來發現是Function Naming造成,例如本來有個方法叫做GetOrders,後來又新增另一個方法叫做GetOrdersAsync,就會讓Swagger用來呈現API定義的頁面失效,要解決此問題不是去調整Swagger,而是調整Function Naming~~不要使用Async結尾的命名方式!!!

原因如下(微軟官方說明)

        從ASP .NET Core 3.0開始,ASP .NET Core MVC會自動把字尾(suffix)的Async移除,routing和link都會受到此預設影響。

解決方法

        方法1:修改性services.AddMvc(options => options.SuppressAsyncSuffixInActionNames = false);

        方法2:Rename Function避免使用Async字尾

MessageKit如何刪除聊天訊息

MessageKit是市面上眾多Chat UI Library之一,比較特別的是每筆訊息都是透過UICollectionView的section代表,而非其他libray常用的UITableView的cell

如果要實現單獨刪除某一筆訊息,可以參考此文Delete message by long press on cell 

但我需要的功能又更進階,希望delte的action只能在部分聊天訊息發生作用,例如我自己傳送失敗的訊息才可以刪除。要達到此目的,只需修改collectionView(_ :canPerformAction:forItemAt:withSender) ~在判斷邏輯上增添自己設計的condition即可(如下圖粉紅色框起處)




Solr基本定義釐清

  • Cluster
    由一群Node組成,並且由ZooKeeper管理。當有新的Core要加入,必須向ZooKeeper註冊
  • Node
    要用來跑Solr的JVM instance,一般來說就是Server。如果有需要的話,一個Node上可以跑好幾個Core。
  • Core
    Server instance,也同時代表Solr index。每個Core擁有自己的Config文件與schema定義文件。
  • Collection
    由一群Core組成,而這些Core來自於同一個single logical index。而每個Collection也擁有自己專屬的Config文件與schema文件。
  • Shard 
    單一Collection的邏輯區塊logical section

Solr簡介

  • 基於Lucene的企業及全文檢索搜尋平台
  • 近實時的索引(查詢)能力 
  • Solr Core是跑在真實Solr伺服器中唯一要被具體命名、可被管理、config的索引,也就是說一台Solr Server可以託管多個Solr Core
  • 承上,Core就是索引,那為何要有多個Core呢?因為不同文件有不同的組成方式,例如欄位不同,像商品資料就和氣象資料差異極大,需要不同的Core來索引與儲存
  • schema是Solr Core中用來定義欄位的文件,包含欄位的名稱、data type...或是這個欄位是否能被索引或儲存
  • schema可以事先定義,也可以透過API動態實現
  • schema更新不用重啟Solr Core,但是舊文件不會因為新的schema而更新舊的索引,只有新文件才會套用新schema
  • Collection是logical的,Core是physical的
  • Solr不提供藉由程式撰寫來Reindex,它的Reindex其實就是index it again,讓整個index的過程重跑一次
  • 需要Reindex的情況有"Schema Changes"與"Upgrade"二種,所以強烈建議將Reindex安排在升級的時候一起作,因為實施的過程中有部分時間無法適用,除非用SolrCloud才能index to another collection
  • SolrCloud = Solr + ZooKeeper
  • 承上,早期作擴展遇到failover都要手動處理,難度很高,後來引入Hadoop常用的ZooKeeper來作failover和load balance,就稱之為SolrCloud

Log Structure Merged Tree簡介

 Log Structure Merged Tree簡稱LSM,閱讀筆記如下:

  • key:value的儲存系統設計
  • 因key:value特性寫入快速,但犧牲讀取速度
  • 原理是一棵大樹,分成多棵小樹,每次寫入都由C0開始,資料量變大後,往上merge為C1,並取代舊的C1
  • key:value的更新僅限於C0層才會即時操作,若是要更新的資料已經被推到上層了,會等到merge的時候才一併處理
  • 需要密集寫入,少量查詢的場景很適合採用LSM

SDWebImageDownloader cannot cache image

有在寫iOS專案的開發者絕對用過大名鼎鼎SDWebImage,絕大多數使用情境都是指定網路圖片url給某個UIImageView,以SDWebImage達到圖片快取的效果。

UIImageView載入網路圖片最簡單的方式透過sd_setImage方法直接取用,更進階的可以使用SDWebImageManager或SDWebImageDownloader,前者的用途兼具下載圖片與快取圖片,後者只能下載。

但有些圖片提供者要求必須在Http header指定安全性驗證用的value,這時候就只能使用無法圖片快取的SDWebImageDownloader

為了解決這個問題,可以搭配SDImageCache使用,SDWebImageDownloader下載完後使用url作為key儲存至memory,下次再遇到相同url只要透過此key即可載入快取給UIImageView使用

程式碼如下

if let imageCache = SDImageCache.shared.imageFromMemoryCache(forKey: myURLString) {

    imageView.image = imageCache

} else {

    SDWebImageDownloader.shared.setValue("myToken", forHTTPHeaderField: "headerName")

    SDWebImageDownloader.shared.downloadImage(with: myURLString) { (image, data, error, finished) in

        if image != nil && finished {

            SDImageCache.shared.storeImage(toMemory: image, forKey: myURLString)

            imageView.image = image

        }

        if error != nil {

            print("[下載圖片失敗] url:\(myURLString), error:\(error!)")

        }

    }

}

Install Package in Sublime Text 2

 成功安裝Package Control後,開始使用它安裝各種plugin套件。

  • 點選選單Preference→Package Control
    輸入Install Package按enter開啟搜尋package的視窗,在視窗內找到想要的plugin安裝
  • 安裝失敗的解決辦法
    請檢查網路是否使用有限制的公司網路,如果你是MAC環境網路通順卻還是不行下載,可參考此篇文章開啟Preference→Package Setting→Package Control→ Settings - User修改,在原始檔案中加入下列設定即可
"downloader_precedence": {
"windows": ["wininet"],
"osx": ["curl", "urllib"],
"linux": ["urllib", "curl", "wget"]
}

Install Package Control in Sublime Text 2

官網安裝教學有自動跟手動二種,自動只適用目前最新版Sublime Text 3,不知道Sublime Text 3何時開始收錢,反正Sublime Text 2夠用,就記錄一下同樣適用2的手動安裝方式。

  1. 開啟Sublime Text 2
  2. 點選選單的Preference→Browse Package...
    會開啟安裝路徑下的Packages資料夾,回到上一層Installed Packages資料夾
  3. 下載package
    在官網下載Package Control.sublime-package,將檔案放到前一步驟的Installed Packages資料夾
  4. 重啟Sublime Text 2
    成功的話選單會新增Package Settings與Package Control二個選項

Cmder安裝與使用

  1. Cmder下載所需安裝檔
    目前只需能執行簡單shell script,不需要Git其他擴充功能,故選擇下載Mini版。
  2. 下載完成解壓至任一資料夾
  3. 設定以系統管理員身份執行(optional)
  4. 寫一個hello script命名為test1.sh
  5. 執行Cmder.exe執行test1.sh,如預期輸出Hello

Installing cURL in Windows

 cURL不存在於Windows,自行安裝過程如下。

  1. 下載對應的壓縮包
    curl for Windows下載
  2. 解壓至想要的路徑
    路徑隨意以個人需求為主,本文將解壓後的curl-7.72.0-win64-mingw資料夾放在C:\curl\
  3. 設定環境變數
    承上,cURL執行檔實際位置在C:\curl\curl-7.72.0-win64-mingw\bin,設定環境變數對後續使用較方便

  4. 檢查是否安裝成功
    cmd視窗輸入curl -V,透過檢查版本號確認環境變數有設定成功可以無痛使用

[iOS]App背景設定滿版圖片

App多數都會在歡迎頁塞個全版的美美背景圖,一般來說做法可以參考這篇如何讓 iOS App 畫面擁有背景圖片

但如果你想要一款背景圖打死所有尺寸,又不希望圖片被拉伸變形,可以在viewDidLoad這樣做

Objective C
self.view.layer.contents = (id)[UIImage imageNamed:@"背景圖檔案名稱"].CGImage;

Swift
self.view.layer.contents = UIImage.init(named:"背景圖檔案名稱")!.cgImage

[iOS]SourceTree error : 'yourXXX.git' does not appear to be a git repository

一個很久沒動的專案,簽入異動至server前,在pull時遭遇如下錯誤
Xcode






Source Tree

後來發現是Remote repository paths不知為何跑掉,參考下圖修改即可

[iOS]限制UITextView的行數與換行規格

UITextView不像UILabel有文字行數、文字斷行規則的屬性可以直接設定,要再往下一層textContainer才能找到相對應的屬性,寫法如下

Swift
textView.textContainer.maximumNumberOfLines = 1
textView.textContainer.lineBreakMode = .byTruncatingTail

Objective C
textView.textContainer.maximumNumberOfLines = 1;
textView.textContainer.lineBreakMode = NSLineBreakByTruncatingTail;

參考文章

[iOS]UIDocumentPickerViewController支援docx, pptx, xlsx格式

依據Apple Document說明,Microsoft文件的UTI如下
doc com.microsoft.word.doc
ppt com.microsoft.powerpoint.ppt
xls com.microsoft.excel.xls

但沒有提到docx, pptx, xlsx格式,後來翻到一篇實用的文章有列出此三款UTI如下
docx org.openxmlformats.wordprocessingml.document
pptx org.openxmlformats.presentationml.presentation
xlsx   org.openxmlformats.spreadsheetml.sheet

經實際測試,不需要設置info.plist,直接使用UTI即可
sample code如下:
NSArray *documentTypes = @[@"com.microsoft.word.doc", @"org.openxmlformats.wordprocessingml.document", @"com.microsoft.excel.xls", @"org.openxmlformats.spreadsheetml.sheet", @"com.microsoft.powerpoint.ppt", @"org.openxmlformats.presentationml.presentation"];
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:documentTypes inMode:UIDocumentPickerModeOpen];
documentPicker.delegate = self;
documentPicker.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:documentPicker animated:YES completion:nil];

[iOS]stringByAddingPercentEscapesUsingEncoding vs stringByAddingPercentEncodingWithAllowedCharacters

'stringByAddingPercentEscapesUsingEncoding:' is deprecated: first deprecated in iOS 9.0 - Use -stringByAddingPercentEncodingWithAllowedCharacters: instead, which always uses the recommended UTF-8 encoding, and which encodes for a specific URL component or subcomponent since each URL component or subcomponent has different rules for what characters are valid.

如上述文字,不要再用過期的stringByAddingPercentEscapesUsingEncoding,請愛用stringByAddingPercentEncodingWithAllowedCharacters

但stringByAddingPercentEncodingWithAllowedCharacters還要指定你要用哪一款Character Sets,像我的網址如下
http://myhost/User/karen_chang@quantatw.com.png?ModifyDate=2018-06-14T15:30:33.819000
我不希望query string後面的string被解譯錯誤,那就要選用URLQueryAllowedCharacterSet

其他五款的選用時機,請參考Apple Document
不要看網路上都用URLHostAllowedCharacterSet就亂抄,請實際依你的網址選用適合的

[iOS]設定UIImage的透明度

平常都是透過UIImageView設定背景色的透明度,但如果要圖片本身帶有透明度效果,可參考How to set the opacity/alpha of a UIImage?這篇文章的作法,參考Nick H247的做法修改如下:

- (UIImage *)imageByApplyingAlpha:(UIImageView *)imageView alpha:(CGFloat) alpha {
    UIGraphicsBeginImageContextWithOptions(imageView.frame.size, NO, 0.0f);
    
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGRect area = CGRectMake(0, 0, imageView.frame.size.width, imageView.frame.size.height);
    
    CGContextScaleCTM(ctx, 1, -1);
    CGContextTranslateCTM(ctx, 0, -area.size.height);
    
    //CGContextSetBlendMode(ctx, kCGBlendModeMultiply);
    
    CGContextSetAlpha(ctx, alpha);
    
    CGContextDrawImage(ctx, area, imageView.image.CGImage);
    
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    UIGraphicsEndImageContext();
    
    return newImage;
}

[iOS]隱藏UITableView的section header

最近產品畫面用了Grouped UITableView,但卻不想要每個Secton Header都出現,想要永遠隱藏第一個Section header,其他都出現。

照理說
tableView:viewForHeaderInSection 方法 return nil
heightForHeaderInSection 方法 return 0
就可以了

但怎麼試都還是在畫面上看到第一個section Header,後來發現heightForHeaderInSection至少要設一個大於零的數字就好,例如設一個超小的0.1 之類的