トップ 最新 追記

Cocoa練習帳

iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど

2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|

2015-01-04 [iOS]動画を90度回転させる(その二)

保存が失敗する原因がわかった。AVMutableVideoCompositionLayerInstructionに設定するトラックが間違えていた!

これで安心して新年をむかえられる。

import UIKit
import AVFoundation
import AssetsLibrary
 
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
 
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
 
    @IBOutlet weak var rotateButton: UIButton!
    
    @IBAction func rotate(sender:AnyObject) {
        println(__FUNCTION__)
        
        if UIImagePickerController.isSourceTypeAvailable(.SavedPhotosAlbum) == false {
            return
        }
        
        let mediaUI = UIImagePickerController()
        mediaUI.sourceType = .SavedPhotosAlbum
        if let mediaTypes = UIImagePickerController.availableMediaTypesForSourceType(.SavedPhotosAlbum) {
            mediaUI.mediaTypes = mediaTypes
            NSLog("%s mediaTypes:%@", __FUNCTION__, mediaTypes)
        }
        
        // mediaUI.mediaTypes = [kUTTypeMovie]
        mediaUI.mediaTypes = ["public.movie"]
        
        mediaUI.allowsEditing = false
        
        mediaUI.delegate = self
        
        self.presentViewController(mediaUI, animated: true, completion: nil)
    }
    
    var mutableComposition: AVMutableComposition?
    
    var mutableVideoComposition: AVMutableVideoComposition?
    
    var exportSession: AVAssetExportSession?
    
    func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject: AnyObject]) {
        println(__FUNCTION__)
        if info[UIImagePickerControllerMediaURL] != nil {
            /* アセットオブジェクトの作成 */
            let url: NSURL = info[UIImagePickerControllerMediaURL] as NSURL
            var options = [String: Bool]()
            options[AVURLAssetPreferPreciseDurationAndTimingKey] = true
            var asset = AVURLAsset(URL: url, options: options)
            NSLog("%s url:%@", __FUNCTION__, url)
            
            /* アセットから動画/音声トラックを取り出す */
            let assetVideoTrack: AVAssetTrack = asset.tracksWithMediaType(AVMediaTypeVideo)[0] as AVAssetTrack
            let assetAudioTrack: AVAssetTrack = asset.tracksWithMediaType(AVMediaTypeAudio)[0] as AVAssetTrack
            
            let insertionPoint: CMTime = kCMTimeZero
            var error: NSError? = nil
            
            /* コンポジションを作成 */
            if mutableComposition == nil {
                mutableComposition = AVMutableComposition()
                
                /* 動画コンポジショントラックの作成 */
                let compositionVideoTrack: AVMutableCompositionTrack = mutableComposition!.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
                
                /* 動画データをコンポジションに追加 */
                error = nil
                compositionVideoTrack.insertTimeRange(CMTimeRangeMake(insertionPoint, assetVideoTrack.timeRange.duration), ofTrack: assetVideoTrack, atTime: kCMTimeZero, error: &error)
                if error != nil {
                    NSLog("%s insertVideoTack error:%@", __FUNCTION__, error!)
                }
                
                /* 音声コンポジショントラックの作成 */
                let compositionAudioTrack: AVMutableCompositionTrack = mutableComposition!.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
                
                /* 音声データをコンポジションに追加 */
                error = nil
                compositionAudioTrack.insertTimeRange(CMTimeRangeMake(insertionPoint, assetAudioTrack.timeRange.duration), ofTrack: assetAudioTrack, atTime: kCMTimeZero, error: &error)
                if error != nil {
                    NSLog("%s insertAudioTrach error:%@", __FUNCTION__, error!)
                }
            }
            var compositionVideoTrack: AVMutableCompositionTrack? = nil
            var compositionAudioTrack: AVMutableCompositionTrack? = nil
            for track in mutableComposition!.tracks {
                if track.isKindOfClass(AVMutableCompositionTrack) {
                    var mutableCompositionTrack = track as AVMutableCompositionTrack
                    if track.mediaType == AVMediaTypeVideo {
                        compositionVideoTrack = mutableCompositionTrack
                    }
                    else if track.mediaType == AVMediaTypeAudio {
                        compositionAudioTrack = mutableCompositionTrack
                    }
                }
            }
            
            var instruction: AVMutableVideoCompositionInstruction
            var layerInstruction: AVMutableVideoCompositionLayerInstruction
            
            /* 移動して回転 */
            let t1: CGAffineTransform = CGAffineTransformMakeTranslation(assetVideoTrack.naturalSize.height, 0.0)
            let t2: CGAffineTransform = CGAffineTransformRotate(t1, ((90.0 / 180.0) * 3.14159265358979323846264338327950288))
            
            if mutableVideoComposition == nil {
                /* 動画コンポジションの作成 */
                mutableVideoComposition = AVMutableVideoComposition()
                mutableVideoComposition!.renderSize = CGSizeMake(assetVideoTrack.naturalSize.height, assetVideoTrack.naturalSize.width)
                mutableVideoComposition!.frameDuration = CMTimeMake(1, 30);
                
                /* 動画コンポジション命令 */
                instruction = AVMutableVideoCompositionInstruction()
                instruction.timeRange = CMTimeRangeMake(kCMTimeZero, mutableComposition!.duration);
                layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionVideoTrack);
                layerInstruction.setTransform(t2, atTime: kCMTimeZero)
                NSLog("%s instruction:%@", __FUNCTION__, instruction)
                NSLog("%s layerInstruction:%@", __FUNCTION__, layerInstruction)
            }
            else {
                mutableVideoComposition!.renderSize = CGSizeMake(mutableVideoComposition!.renderSize.height, mutableVideoComposition!.renderSize.width);
                
                /* 動画コンポジション命令の抽出 */
                instruction = mutableVideoComposition!.instructions[0] as AVMutableVideoCompositionInstruction
                layerInstruction = instruction.layerInstructions[0] as AVMutableVideoCompositionLayerInstruction
                
                /* 内容の確認 */
                var existingTransform = CGAffineTransform(a: 0.0, b: 0.0, c: 0.0, d: 0.0, tx: 0.0, ty: 0.0)
                if layerInstruction.getTransformRampForTime(mutableComposition!.duration, startTransform: &existingTransform, endTransform: nil, timeRange: nil) == false {
                    layerInstruction.setTransform(t2, atTime: kCMTimeZero)
                }
                else {
                    /* 原点補償 */
                    let t3: CGAffineTransform = CGAffineTransformMakeTranslation(-1.0 * assetVideoTrack.naturalSize.height / 2.0, 0.0)
                    let newTransform: CGAffineTransform = CGAffineTransformConcat(existingTransform, CGAffineTransformConcat(t2, t3))
                    layerInstruction.setTransform(newTransform, atTime: kCMTimeZero)
                }
            }
            
            /* コンポジションに命令を追加 */
            instruction.layerInstructions = [layerInstruction]
            mutableVideoComposition!.instructions = [instruction]
            
            /* 出力URL */
            var documentsPath: NSString = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
            error = nil
            NSFileManager.defaultManager().createDirectoryAtPath(documentsPath, withIntermediateDirectories: true, attributes: nil, error: &error)
            if error != nil {
                NSLog("%s createDir error:%@", __FUNCTION__, error!)
            }
            let now = NSDate()
            let dateFormatter = NSDateFormatter()
            dateFormatter.locale = NSLocale(localeIdentifier: "ja_JP")
            dateFormatter.dateFormat = "yyyyMMddHHmmss"
            let filename = String(format: "%@.mp4", arguments: [dateFormatter.stringFromDate(now)])
            var exportPath: NSString = documentsPath.stringByAppendingPathComponent(filename)
            error = nil
            //NSFileManager.defaultManager().removeItemAtPath(exportPath, error: &error)
            if error != nil {
                NSLog("%s removeFile error:%@", __FUNCTION__, error!)
            }
            var exportUrl: NSURL = NSURL.fileURLWithPath(exportPath)!
            
            /* セッションを作成し、フォトライブラリに書き出す */
            exportSession = AVAssetExportSession(asset: mutableComposition!.copy() as AVAsset, presetName: AVAssetExportPresetHighestQuality)
            exportSession!.videoComposition = mutableVideoComposition
            exportSession!.outputURL = exportUrl
            exportSession!.outputFileType = AVFileTypeQuickTimeMovie
            
            exportSession!.exportAsynchronouslyWithCompletionHandler({
                () -> Void in
                NSLog("%@", __FUNCTION__)
                switch self.exportSession!.status {
                case AVAssetExportSessionStatus.Completed:
                    NSLog("%@ AVAssetExportSessionStatus.Completed", __FUNCTION__)
                    let assetsLib = ALAssetsLibrary()
                    assetsLib.writeVideoAtPathToSavedPhotosAlbum(exportUrl, completionBlock: {
                        (nsurl, error) -> Void in
                        if error != nil {
                            NSLog("%@ error:%@", __FUNCTION__, error)
                        }
                    })
                case AVAssetExportSessionStatus.Failed:
                    NSLog("%@ AVAssetExportSessionStatus.Failed exporter:%@ error:%@", __FUNCTION__, self.exportSession!, self.exportSession!.error)
                case AVAssetExportSessionStatus.Cancelled:
                    NSLog("%@ AVAssetExportSessionStatus.Cancelled exporter:%@ error:%@", __FUNCTION__, self.exportSession!, self.exportSession!.error)
                default:
                    NSLog("%@ none exporter:%@", __FUNCTION__, self.exportSession!)
                }
            })
        }
        picker.dismissViewControllerAnimated(true, completion: nil);
    }
 
}

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/AVEditor - GitHub

_ 【Cocoa練習帳】

http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

2015-01-16 [Android]AsyncTaskとUIスレッド

AsyncTaskの仕様をよく読むと気になることが説明されている。UIスレッドで使用するという箇所だ。

Threading rulesでスレッド関連の説明がされているが、クラスのロードはUIスレッド上で。ただし、JELLY_BEANからは自動で対応している。インスタンスはUIスレッドで生成すること。

ソースを確認してみよう。Android SDKが置かれているディレクトリ配下で以下のコマンドを実行。

$ cd /Applications/Development/android-sdk-macosx
$ find . -name AsyncTask.java -print
./samples/android-19/ui/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/AsyncTask.java
./samples/android-21/ui/DisplayingBitmaps/Application/src/main/java/com/example/android/displayingbitmaps/util/AsyncTask.java
./sources/android-19/android/os/AsyncTask.java
./sources/android-21/android/os/AsyncTask.java

Android 5.0 Lollipopのソースを確認してみよう。

public abstract class AsyncTask<Params, Progress, Result> {
        :
    private static final InternalHandler sHandler = new InternalHandler();
        :
    /** @hide Used to force static handler to be created. */
    public static void init() {
        sHandler.getLooper();
    }
        :
}

AsyncTaskのHandlerはクラス変数になっている。そして、HandlerのLooperはクラスメソッドで設定されている。つまり、最初にAsyncTaskのインスタンスを生成する際にHandlerとLooperが保持されることになる。

なぜ、HandlerとLooperが必要か?それは、onCancelledなど、UIスレッドで呼び出すことになっているメソッドがUIスレッドで呼ばれるようにする為だろう。こういうわけだ、AsyncTaskはUIスレッドで生成されることになるので、最初の生成時にUIスレッドのLooperを保持しておいて、別スレッドで処理を終えた後に、このLooperを使って、UIスレッドで結果を返す。

なので、最初のAsyncTaskの生成をUIスレッド以外で行うと、そのスレッドにLooperがない場合は、myLooperがnullの例外が発生することになる。Looperが存在しても、onCencelledなどがUIスレッドとは異なるスレッドで呼ば出されることになってしまう。

気をつけよう。著者はこれでハマった。

_ 関連情報

AsyncTask

_ 【Cocoa練習帳】

http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

2015-01-28 [OSX][iOS]UDP

UDP通信のサンプルを作成したので、ざっと説明する。

ADCのサンプルコードUDPEchoを改造して作成した。

UDP通信する部分はクラスにしている。

@protocol UDPDelegate;
 
@interface UDP : NSObject
 
@property (nonatomic, weak,   readwrite) id<UDPDelegate>        delegate;
@property (nonatomic, assign, readonly, getter=isServer) BOOL   server;
@property (nonatomic, copy,   readonly ) NSString *             hostName;
@property (nonatomic, copy,   readonly ) NSData *               hostAddress;
@property (nonatomic, assign, readonly ) NSUInteger             port;
 
- (void)startServerOnPort:(NSUInteger)port;
- (void)startConnectedToHostName:(NSString *)hostName port:(NSUInteger)port;
- (void)sendData:(NSData *)data;
- (void)stop;
 
@end
 
@protocol UDPDelegate <NSObject>
@optional
- (void)udp:(UDP *)udp didReceiveData:(NSData *)data fromAddress:(NSData *)addr;
- (void)udp:(UDP *)udp didReceiveError:(NSError *)error;
- (void)udp:(UDP *)udp didSendData:(NSData *)data toAddress:(NSData *)addr;
- (void)udp:(UDP *)udp didFailToSendData:(NSData *)data toAddress:(NSData *)addr error:(NSError *)error;
- (void)udp:(UDP *)udp didStartWithAddress:(NSData *)address;
- (void)udp:(UDP *)udp didStopWithError:(NSError *)error;
@end
#import <sys/socket.h>
#import <netinet/in.h>
#import <fcntl.h>
#import <unistd.h>
#import <Foundation/Foundation.h>
 
#if TARGET_OS_EMBEDDED || TARGET_IPHONE_SIMULATOR
#import <CFNetwork/CFNetwork.h>
#else
#import <oreServices/CoreServices.h>
#endif
 
#import "UDP.h"
 
@interface UDP () 
@property (nonatomic, copy,   readwrite) NSString *             hostName;
@property (nonatomic, copy,   readwrite) NSData *               hostAddress;
@property (nonatomic, assign, readwrite) NSUInteger             port;
 
- (void)stopHostResolution;
- (void)stopWithError:(NSError *)error;
- (void)stopWithStreamError:(CFStreamError)streamError;
@end
 
@implementation UDP {
    CFHostRef   _cfHost;
    CFSocketRef _cfSocket;
}
 
- (id)init
{
    NSLog(@"%s", __func__);
    self = [super init];
    if (self != nil) {
    }
    return self;
}
 
- (void)dealloc
{
    NSLog(@"%s", __func__);
    [self stop];
}
 
- (BOOL)isServer
{
    NSLog(@"%s", __func__);
    return self.hostName == nil;
}
 
- (void)sendData:(NSData *)data toAddress:(NSData *)addr
{
    NSLog(@"%s", __func__);
    int                     err;
    int                     sock;
    ssize_t                 bytesWritten;
    const struct sockaddr * addrPtr;
    socklen_t               addrLen;
    
    sock = CFSocketGetNative(self->_cfSocket);
    
    if (addr == nil) {
        addr = self.hostAddress;
        addrPtr = NULL;
        addrLen = 0;
    } else {
        addrPtr = [addr bytes];
        addrLen = (socklen_t) [addr length];
    }
    
    bytesWritten = sendto(sock, [data bytes], [data length], 0, addrPtr, addrLen);
    if (bytesWritten < 0) {
        err = errno;
    } else  if (bytesWritten == 0) {
        err = EPIPE;
    } else {
        err = 0;
    }
    
    if (err == 0) {
        if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didSendData:toAddress:)] ) {
            [self.delegate udp:self didSendData:data toAddress:addr];
        }
    } else {
        if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didFailToSendData:toAddress:error:)] ) {
            [self.delegate udp:self
             didFailToSendData:data
                     toAddress:addr
                         error:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
        }
    }
}
 
- (void)readData
{
    NSLog(@"%s", __func__);
    int                     err;
    int                     sock;
    struct sockaddr_storage addr;
    socklen_t               addrLen;
    uint8_t                 buffer[65536];
    ssize_t                 bytesRead;
    
    sock = CFSocketGetNative(self->_cfSocket);
    
    addrLen = sizeof(addr);
    bytesRead = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *) &addr, &addrLen);
    if (bytesRead < 0) {
        err = errno;
    } else if (bytesRead == 0) {
        err = EPIPE;
    } else {
        NSData *dataObj;
        NSData *addrObj;
        
        err = 0;
        
        dataObj = [NSData dataWithBytes:buffer length:(NSUInteger) bytesRead];
        addrObj = [NSData dataWithBytes:&addr  length:addrLen  ];
        
        if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didReceiveData:fromAddress:)] ) {
            [self.delegate udp:self didReceiveData:dataObj fromAddress:addrObj];
        }
        
        if (self.isServer) {
            [self sendData:dataObj toAddress:addrObj];
        }
    }
    
    if (err != 0) {
        if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didReceiveError:)] ) {
            [self.delegate udp:self
               didReceiveError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
        }
    }
}
 
static void SocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
{
    NSLog(@"%s", __func__);
    UDP *udp = (__bridge UDP *)info;
    [udp readData];
}
 
- (BOOL)setupSocketConnectedToAddress:(NSData *)address port:(NSUInteger)port error:(NSError **)errorPtr
{
    NSLog(@"%s", __func__);
    int                     err;
    int                     junk;
    int                     sock;
    const CFSocketContext   context = { 0, (__bridge void *)(self), NULL, NULL, NULL };
    CFRunLoopSourceRef      rls;
    
    err = 0;
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        err = errno;
    }
    
    if (err == 0) {
        struct sockaddr_in      addr;
        
        memset(&addr, 0, sizeof(addr));
        if (address == nil) {
            addr.sin_len         = sizeof(addr);
            addr.sin_family      = AF_INET;
            addr.sin_port        = htons(port);
            addr.sin_addr.s_addr = INADDR_ANY;
            err = bind(sock, (const struct sockaddr *) &addr, sizeof(addr));
        } else {
            if ([address length] > sizeof(addr)) {
                [address getBytes:&addr length:sizeof(addr)];
            } else {
                [address getBytes:&addr length:[address length]];
            }
            addr.sin_port = htons(port);
            err = connect(sock, (const struct sockaddr *) &addr, sizeof(addr));
        }
        if (err < 0) {
            err = errno;
        }
    }
    
    if (err == 0) {
        int flags;
        
        flags = fcntl(sock, F_GETFL);
        err = fcntl(sock, F_SETFL, flags | O_NONBLOCK);
        if (err < 0) {
            err = errno;
        }
    }
    
    if (err == 0) {
        self->_cfSocket = CFSocketCreateWithNative(NULL, sock, kCFSocketReadCallBack, SocketReadCallback, &context);
        
        sock = -1;
        
        rls = CFSocketCreateRunLoopSource(NULL, self->_cfSocket, 0);
        
        CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
        
        CFRelease(rls);
    }
    
    if (sock != -1) {
        junk = close(sock);
    }
    if ( (self->_cfSocket == NULL) && (errorPtr != NULL) ) {
        *errorPtr = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil];
    }
    
    return (err == 0);
}
 
- (void)startServerOnPort:(NSUInteger)port
{
    NSLog(@"%s", __func__);
    if (self.port == 0) {
        BOOL        success;
        NSError *   error;
        
        success = [self setupSocketConnectedToAddress:nil port:port error:&error];
        
        if (success) {
            self.port = port;
            
            if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didStartWithAddress:)] ) {
                CFDataRef   localAddress;
                
                localAddress = CFSocketCopyAddress(self->_cfSocket);
                
                [self.delegate udp:self didStartWithAddress:(__bridge NSData *) localAddress];
                
                CFRelease(localAddress);
            }
        } else {
            [self stopWithError:error];
        }
    }
}
 
- (void)hostResolutionDone
{
    NSLog(@"%s", __func__);
    NSError *           error;
    Boolean             resolved;
    NSArray *           resolvedAddresses;
    
    error = nil;
    
    resolvedAddresses = (__bridge NSArray *) CFHostGetAddressing(self->_cfHost, &resolved);
    if ( resolved && (resolvedAddresses != nil) ) {
        for (NSData * address in resolvedAddresses) {
            BOOL                    success;
            const struct sockaddr * addrPtr;
            NSUInteger              addrLen;
            
            addrPtr = (const struct sockaddr *) [address bytes];
            addrLen = [address length];
            
            success = NO;
            if (
                (addrPtr->sa_family == AF_INET)
                ) {
                success = [self setupSocketConnectedToAddress:address port:self.port error:&error];
                if (success) {
                    CFDataRef   hostAddress;
                    
                    hostAddress = CFSocketCopyPeerAddress(self->_cfSocket);
                    
                    self.hostAddress = (__bridge NSData *) hostAddress;
                    
                    CFRelease(hostAddress);
                }
            }
            if (success) {
                break;
            }
        }
    }
    
    if ( (self.hostAddress == nil) && (error == nil) ) {
        error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorHostNotFound userInfo:nil];
    }
    
    if (error == nil) {
        [self stopHostResolution];
        
        if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didStartWithAddress:)] ) {
            [self.delegate udp:self didStartWithAddress:self.hostAddress];
        }
    } else {
        [self stopWithError:error];
    }
}
 
static void HostResolveCallback(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info)
{
    NSLog(@"%s", __func__);
    UDP *udp;
    
    udp = (__bridge UDP *)info;
    
    if ( (error != NULL) && (error->domain != 0) ) {
        [udp stopWithStreamError:*error];
    } else {
        [udp hostResolutionDone];
    }
}
 
- (void)startConnectedToHostName:(NSString *)hostName port:(NSUInteger)port
{
    NSLog(@"%s", __func__);
    if (self.port == 0) {
        Boolean             success;
        CFHostClientContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
        CFStreamError       streamError;
        
        self->_cfHost = CFHostCreateWithName(NULL, (__bridge CFStringRef) hostName);
        
        CFHostSetClient(self->_cfHost, HostResolveCallback, &context);
        
        CFHostScheduleWithRunLoop(self->_cfHost, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
        
        success = CFHostStartInfoResolution(self->_cfHost, kCFHostAddresses, &streamError);
        if (success) {
            self.hostName = hostName;
            self.port = port;
        } else {
            [self stopWithStreamError:streamError];
        }
    }
}
 
- (void)sendData:(NSData *)data
{
    NSLog(@"%s", __func__);
    if (self.isServer || (self.hostAddress == nil) ) {
    } else {
        [self sendData:data toAddress:nil];
    }
}
 
- (void)stopHostResolution
{
    NSLog(@"%s", __func__);
    if (self->_cfHost != NULL) {
        CFHostSetClient(self->_cfHost, NULL, NULL);
        CFHostCancelInfoResolution(self->_cfHost, kCFHostAddresses);
        CFHostUnscheduleFromRunLoop(self->_cfHost, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
        CFRelease(self->_cfHost);
        self->_cfHost = NULL;
    }
}
 
- (void)stop
{
    NSLog(@"%s", __func__);
    self.hostName = nil;
    self.hostAddress = nil;
    self.port = 0;
    [self stopHostResolution];
    if (self->_cfSocket != NULL) {
        CFSocketInvalidate(self->_cfSocket);
        CFRelease(self->_cfSocket);
        self->_cfSocket = NULL;
    }
}
 
- (void)noop
{
    NSLog(@"%s", __func__);
}
 
- (void)stopWithError:(NSError *)error
{
    NSLog(@"%s", __func__);
    [self stop];
    if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didStopWithError:)] ) {
        [self performSelector:@selector(noop) withObject:nil afterDelay:0.0];
        [self.delegate udp:self didStopWithError:error];
    }
}
 
- (void)stopWithStreamError:(CFStreamError)streamError
{
    NSLog(@"%s", __func__);
    NSDictionary *  userInfo;
    NSError *       error;
    
    if (streamError.domain == kCFStreamErrorDomainNetDB) {
        userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                    [NSNumber numberWithInteger:streamError.error], kCFGetAddrInfoFailureKey,
                    nil
                    ];
    } else {
        userInfo = nil;
    }
    error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:userInfo];
    
    [self stopWithError:error];
}
 
@end

そして、ViewControllerから呼び出す。

#import <UIKit/UIKit.h>
 
@interface ViewController : UIViewController
 
@property (weak, nonatomic) IBOutlet UITextField *inputTextField;
@property (weak, nonatomic) IBOutlet UIButton *sendButton;
@property (weak, nonatomic) IBOutlet UILabel *outputLabel;
 
- (IBAction)send:(id)sender;
 
@end
#import "ViewController.h"
#import "UDP.h"
 
@interface ViewController () <UDPDelegate>
 
@property UDP *server;
@property UDP *client;
 
- (void)runServerOnPort:(NSUInteger)port;
- (void)runClientWithHost:(NSString *)host port:(NSUInteger)port;
@end
 
@implementation ViewController
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self runServerOnPort:3054];
    [self runClientWithHost:@"localhost" port:3054];
}
 
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}
 
- (IBAction)send:(id)sender
{
    NSLog(@"%s", __func__);
    NSData *data = [[NSString stringWithString:self.inputTextField.text] dataUsingEncoding:NSUTF8StringEncoding];
    [self.client sendData:data];
}
 
- (void)runServerOnPort:(NSUInteger)port
{
    NSLog(@"%s", __func__);
    self.server = [[UDP alloc] init];
    self.server.delegate = self;
    [self.server startServerOnPort:port];
}
 
- (void)runClientWithHost:(NSString *)host port:(NSUInteger)port
{
    NSLog(@"%s", __func__);
    self.client = [[UDP alloc] init];
    self.client.delegate = self;
    [self.client startConnectedToHostName:host port:port];
}
 
- (void)udp:(UDP *)udp didReceiveData:(NSData *)data fromAddress:(NSData *)addr
{
    NSLog(@"%s data(%@)", __func__, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    self.outputLabel.text = msg;
}
 
- (void)udp:(UDP *)udp didReceiveError:(NSError *)error
{
    NSLog(@"%s", __func__);
    self.outputLabel.text = [error description];
}
 
- (void)udp:(UDP *)udp didSendData:(NSData *)data toAddress:(NSData *)addr
{
    NSLog(@"%s data(%@)", __func__, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
 
- (void)udp:(UDP *)udp didFailToSendData:(NSData *)data toAddress:(NSData *)addr error:(NSError *)error
{
    NSLog(@"%s", __func__);
    NSLog(@"failed with error: %@", [error description]);
}
 
- (void)udp:(UDP *)udp didStartWithAddress:(NSData *)address
{
    NSLog(@"%s", __func__);
}
 
- (void)udp:(UDP *)udp didStopWithError:(NSError *)error
{
    NSLog(@"%s", __func__);
    NSLog(@"failed with error: %@", [error description]);
}
 
@end

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/UDP - GitHub

_ 【Cocoa練習帳】

http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)