64111


以下の2つの記事を参考に、Swiftで[BlackRaccoon](http://lloydsargent.github.io/BlackRaccoon/)というライブラリを使用するテストをしてみました。  
[SwiftでFTPを使用する準備を行う](https://www.paveway.info/entry/2018/09/25/swift_ftp_preparation)  
[SwiftのFTPでファイルを作成する](https://www.paveway.info/entry/2018/09/27/swift_ftp_createfile)

BlackRacoonはObjective-Cで書かれたライブラリです。  
BlackRacoonのファイルをDLして、プロジェクトに追加しています。  
Swiftで使用するために、Bridging-Header.hでBlackRaccoonのObjective-Cファイルをimportしています。

以下はViewControllerの内容です。  
ボタンを押すと、FTPアップロードをするような仕様になっています。  
しかし、実際にビルドすると、ビルド前にはエラーが出ていないにも関わらずビルド後にエラーになります。

[code]

    class ViewController: UIViewController {
    
        @IBOutlet weak var FTPuploadButton: UIButton!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
    
            self.view.addSubview(FTPuploadButton)
        }
    
        //ボタンを押したとき
        @IBAction func FTPuploadButtonPush(_ sender: Any) {
            self.ftpupload()
        }
    
        //ftpアップロード
        func ftpupload()
        {
            let testList = ["test1","test2"]
    
            do {
                // ファイルに書き込み
                let jsonTextFileName = "test.txt"
                let jsonData = try JSONSerialization.data(withJSONObject: testList, options: [])
                let jsonStr = String(bytes: jsonData, encoding: .utf8)!
                self.writeFile(file_name: jsonTextFileName,text: jsonStr)
    
                if let dir = FileManager.default.urls( for: .documentDirectory, in: .userDomainMask ).first
                 {
                     //urlをpathに変換
                     let pathURL = dir.appendingPathComponent( jsonTextFileName )
                     let pathString:String = pathURL.path
                     //FTPファイルアップロード
                     let ftpFunctions = FtpCreateClass()
                     ftpFunctions.createFile(pathName: pathString)
                 }
            } catch let error {
                print(error)
            }
        }
    
        func writeFile(file_name: String,text: String)
        {
            if let dir = FileManager.default.urls( for: .documentDirectory, in: .userDomainMask ).first
            {
                let path_file_name = dir.appendingPathComponent( file_name )
    
                do {
                    try text.write( to: path_file_name, atomically: false, encoding: String.Encoding.utf8 )
                } catch {
                    //エラー処理
                }
            }
        }
    }
    
    class FtpCreateClass: BRRequestDelete {
    
        // 2. FTPアップロードオブジェクト
        var ftpUpload: BRRequestUpload!
    
        /// FTPアップロードデータ(空データ)
        var ftpUploadData: Data?
    
        /**
         FTPでファイル一覧の取得を開始します。
         */
        func createFile(pathName: String) {
            if ftpUpload != nil {
                // FTPアップロードオブジェクトが有効な場合、2重起動防止のため処理を終了します。
                return
            }
    
            // 3. FTPアップロードオブジェクトを生成し、FTPで接続するパラメータを設定します。
            ftpUpload = BRRequestUpload(delegate: self)
            ftpUpload.hostname = "192.168.1.2"
            ftpUpload.username = "testUser"
            ftpUpload.password = "testPassword"
            ftpUpload.path = pathName
    
            // 4. ファイル作成を開始します。
            ftpUpload.start()
        }
    
        /**
         5. FTPのリクエストが完了した時に呼び出されます。
         - Parameter request: FTPリクエスト
         */
        func requestCompleted(_ request: BRRequest) {
            if request == ftpUpload {
                // FTPリクエストがFTPアップロードオブジェクトと等しい場合
                // 正常終了の場合の処理を記述してください。
    
                // 6. 2重起動防止のため、FTPアップロードオブジェクトをクリアします。
                ftpUpload = nil
            }
        }
    
        /**
         5. FTPリクエストがエラーした時に呼び出されれます。
         - Parameter request: FTPリクエスト
         */
        func requestFailed(_ request: BRRequest) {
            if request == ftpUpload {
                // FTPリクエストがFTPアップロードオブジェクトと等しい場合
                // エラー終了の場合の処理を記述してください。
    
                // 6. 2重起動防止のため、FTPアップロードオブジェクトをクリアします。
                ftpUpload = nil
            }
        }
    
        /**
         5. 上書きリクエスト時に呼び出されます。
         - Parameter request: FTPリクエスト
         */
        func shouldOverwriteFileWithRequest(_ request: BRRequest) -> Bool {
            if request == ftpUpload {
                // FTPリクエストがFTPアップロードオブジェクトと等しい場合
                // 上書きを許可します。
                return true
    
            } else {
                // 上記以外、上書きを禁止します。
                return false
            }
        }
    
        /**
         5. アップロードデータを送信します。
         - Parameter request: リクエスト
         - Returns: アップロードデータ
         */
        func requestData(toSend request: BRRequestUpload) -> Data {
            if let ftpUploadData = ftpUploadData {
                // FTPアップロードデータが有効な場合
                self.ftpUploadData = nil
                return ftpUploadData
    
            } else {
                return Data()
            }
        }
    }
    
[/code]

エラー内容は以下のようになっています。

[code]

    2020-03-25 10:25:26.046663+0900 FTP[89447:8129251] -[FTP.FtpCreateClass requestFailed:]: unrecognized selector sent to instance 0x60000369ad90
    
    2020-03-25 10:25:26.191087+0900 FTP[89447:8129251] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[FTP.FtpCreateClass requestFailed:]: unrecognized selector sent to instance 0x60000369ad90'
    
    *** First throw call stack:
    
    (
    
        0   CoreFoundation                      0x00007fff23c7127e __exceptionPreprocess + 350
    
        1   libobjc.A.dylib                     0x00007fff513fbb20 objc_exception_throw + 48
    
        2   CoreFoundation                      0x00007fff23c91fd4 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
    
        3   CoreFoundation                      0x00007fff23c75c4c ___forwarding___ + 1436
    
        4   CoreFoundation                      0x00007fff23c77f78 _CF_forwarding_prep_0 + 120
    
        5   FTP                                 0x000000010840aa91 -[BRStreamInfo streamError:errorCode:] + 241
    
        6   FTP                                 0x000000010840cd56 -[BRRequestUpload start] + 262
    
        7   FTP                                 0x00000001084140bf $s3FTP14FtpCreateClassC10createFile8pathNameySS_tF + 1791
    
        8   FTP                                 0x0000000108412115 $s3FTP14ViewControllerC9ftpuploadyyF + 1973
    
        9   FTP                                 0x0000000108411877 $s3FTP14ViewControllerC19FTPuploadButtonPushyyypF + 55
    
        10  FTP                                 0x00000001084118d0 $s3FTP14ViewControllerC19FTPuploadButtonPushyyypFTo + 80
    
        11  UIKitCore                           0x00007fff48093fff -[UIApplication sendAction:to:from:forEvent:] + 83
    
        12  UIKitCore                           0x00007fff47a6c00e -[UIControl sendAction:to:forEvent:] + 223
    
        13  UIKitCore                           0x00007fff47a6c358 -[UIControl _sendActionsForEvents:withEvent:] + 398
    
        14  UIKitCore                           0x00007fff47a6b2b7 -[UIControl touchesEnded:withEvent:] + 481
    
        15  UIKitCore                           0x00007fff480cebbf -[UIWindow _sendTouchesForEvent:] + 2604
    
        16  UIKitCore                           0x00007fff480d04c6 -[UIWindow sendEvent:] + 4596
    
        17  UIKitCore                           0x00007fff480ab53b -[UIApplication sendEvent:] + 356
    
        18  UIKitCore                           0x00007fff4812c71a __dispatchPreprocessedEventFromEventQueue + 6847
    
        19  UIKitCore                           0x00007fff4812f1e0 __handleEventQueueInternal + 5980
    
        20  CoreFoundation                      0x00007fff23bd4471 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    
        21  CoreFoundation                      0x00007fff23bd439c __CFRunLoopDoSource0 + 76
    
        22  CoreFoundation                      0x00007fff23bd3b74 __CFRunLoopDoSources0 + 180
    
        23  CoreFoundation                      0x00007fff23bce87f __CFRunLoopRun + 1263
    
        24  CoreFoundation                      0x00007fff23bce066 CFRunLoopRunSpecific + 438
    
        25  GraphicsServices                    0x00007fff384c0bb0 GSEventRunModal + 65
    
        26  UIKitCore                           0x00007fff48092d4d UIApplicationMain + 1621
    
        27  FTP                                 0x00000001084171fb main + 75
    
        28  libdyld.dylib                       0x00007fff5227ec25 start + 1
    
        29  ???                                 0x0000000000000001 0x0 + 1
    
    )
    
    libc++abi.dylib: terminating with uncaught exception of type NSException
    
    (lldb) 
    
[/code]

XCodeのプロジェクト全体は、以下のGitHubに置きました。  
<https://github.com/taichi-suzuki-pckg/BlackRaccoonTest/tree/master>

エラーの原因は何なのでしょうか？  
どうしたら治りますでしょうか？


CC BY-SA 4.0
2020-03-25T01:53:15.637
64096
2020-03-25T14:14:34.340
34471
question
swift network ftp
BlackRaccoonを使用してSwiftでFTPアップロードする方法


**_エラーの原因は何なのでしょうか？_**

エラーログの以下に示した部分が全てを物語っています。

[code]

    2020-03-25 10:25:26.046663+0900 FTP[89447:8129251] -[FTP.FtpCreateClass requestFailed:]: unrecognized selector sent to instance 0x60000369ad90
    
[/code]

これは、`FTP.FtpCreateClass`と言うクラスのインスタンスに対して`requestFailed:`という(ObjCの)メソッドを実行しようとしたが認識されなかった、つまり、

**`FTP.FtpCreateClass`と言うクラスにはObjective-C側から見える`requestFailed:`というメソッドがない**

と言うことを表しています。

あなたの`FtpCreateClass`には`requestFailed(_:)`というメソッドが定義されていますが、これはSwiftのメソッドであり、Objective-
C側からは見えません。

* * *

**_どうしたら治りますでしょうか？_**

Swiftで書かれたメソッドをObjective-
C側にも見せるようにするには`@objc`なんてものを付加する方法もあるのですが、リンク先の記事ではBlackRaccoon側から呼ばれるメソッドにそんなものは付いていません。

どこで差がついてしまっているのかというと、リンク先のこの内容:

>   1. FTPファイルを作成するViewControllerに`BRReqeuestDelegate`を設定します。
>

あなたは、これを無視してしまっています。(と言うかちょっとしたタイプミス?)

`FtpCreateClass`のヘッダは`BRReqeuestDelegate`の実装を宣言していないといけません。

[code]

    class FtpCreateClass: NSObject, BRReqeuestDelegate　{ //<-BRRequestDelete ではない
    
[/code]

`BRReqeuestDelegate`はObjective-
C用のプロトコル(正確な言い方ではないのですが、ここでは細かいことは省略します)なので、`NSObject`も継承させる必要があります。

(これに関連して他にも細かい修正が必要になる可能性がありますが、もしご自身で解決できない事柄が出てきたら、コメントでお知らせください。)

このように`BRReqeuestDelegate`を実装していることを宣言すると、そのプロトコルで宣言されたメソッド(`requestFailed(_:)`とか)はObjective-
C側にも見えるようにコンパイルされますので、ご質問に書かれたような実行時エラーは起こらなくなるはずです。

* * *

`requestFailed:`が呼ばれると言うことは、FTPリクエストそのものは失敗しているので、まだまだあれこれいじらないといけないでしょうが、その辺はまた別の問題ということで。

ちなみにFTPという通信プロトコルは、セキュリティー的には全くのダメダメで、FTP
clientそのものを作りたい、と言ったような「どうしても何があっても絶対にFTPでなければいけないんだ」と言う絶対不可避の必然性がない限り、実際のアプリには使用しないことをお勧めします。

* * *

(追記)  
コメントでいただいた件ですが、エディタ上にはFix it表示が出ず、直し方が見つけにくいかもと思ったのでここに書いておきます。

Xcode 11.3.1での確認ですが、`shouldOverwriteFileWithRequest`は次のようなメソッド名にしないといけないようです。

[code]

        func shouldOverwriteFile(with request: BRRequest) -> Bool {
    
[/code]

既にご自身で気づかれて、直してしまっているかも知れませんが、念のためと言うことで。


CC BY-SA 4.0
2020-03-25T11:51:25.183
64111
2020-03-25T14:14:34.340
2020-03-25T14:14:34.340
13972
13972
64096
answer