iPhone
iOS
Swift

SwiftでObjCも利用したアプリ開発のときにハマったこと

More than 3 years have passed since last update.

192_ 体力のない _-_いなげな言葉いじり.jpg


スカラー型のキャストはコンバージョンを使う

Swiftではスカラー型のキャスト(Casting)はありません。その代わりにコンバージョン(Conversion)を利用します。

let i: Int = Int(1.0)

let n: NSInteger = NSInteger(1.0)

let f: Float = Float(100)
let d: Double = Double(100)
let g: CGFloat = CGFloat(100)


ObjCライクなキャストはできない

ObjCのようなキャスト構文は文法エラーになります。

let index: NSInteger = 1

let width: CGFloat = 100
let r = width * (CGFloat)index
// => ERROR Consecutive statements on a line must be separated by ';'


補足

オブジェクト型はキャスト可能です。


CGFloatとFloatまたはDoubleの演算でエラーになる場合がある

Swiftで扱うCGFloatはビルドするアーキテクチャによってエイリアスが異なります。

シミュレーターがiPhone5s(arm64)とiPhone5,iPhone4s(armv7, armv7s)では挙動が変わります。

アーキテクチャ
CGflotのエイリアス

arm64
Double

armv7, armv7s
Float

一見、コンパイルが通りそうですがiPhone5sシミュレーターでコンパイルするとエラーになります。

let index: NSInteger = 1

let width: CGFloat = 100
let r1 = width * Float(index)
// => ERROR Could not find an overload for '*' that accepts the supplied arguments

この場合は、Doubleでコンバージョンすると演算可能です。

let index: NSInteger = 1

let width: CGFloat = 100
let r2 = width * Double(index)
// => OK

CGFloatとの演算は常ににCGFloatにコンバージョンして演算することで、この問題を回避できます。

let index: NSInteger = 1

let width: CGFloat = 100
let r = width * CGFloat(index)
// => OK


ObjCのNSIntegerとSwiftのIntの演算

NSIntegerとSwiftのInttypealiasされた同じ型です。

// typealias NSInteger = Int

let x: CGFloat = 3
let y: Float = 3
let i: Int = 1
let n: NSInteger = 1
let w = i + Int(x) + Int(y) // => OK
let q = n + Int(x) + Int(y) // => OK

if n == i {
NSLog("HERE!") // => OK
}

SwiftではNSUIntegerを利用できません。

let u: NSUInteger = 1

// ERROR => Use of undeclared type 'NSUInteger'; did you mean to use 'Int'?


ObjCのid型をパラメータにとるメソッドにSwiftのクロージャを渡す方法

以下の様なObjCメソッドにSwiftのクロージャを渡す方法です。

@interface HogeFuga : NSObject 

+ (void) hogeUsingBlock:(id)bock;
@end

ObjCのid型はSwiftのAnyObject?型と同義です。

クロージャは対応した型を有するのでAnyObject型にはなりません。

よってid型にクロージャを代入できない問題が発生します。

HogeFuga.hogeUsingBlock( { () -> () in

println("Fuga")
})
// => ERROR Type '() -> ()' does not conform to protocol 'AnyObject'

var closures: AnyObject = { () -> () in
println("Fuga")
}
// => ERROR Type '() -> ()' does not conform to protocol 'AnyObject'

解決策はObjCで型を明示したブロックを持つメソッドでラップします。

typedef void (^BlockHogeWrapper)();

@interface HogeFuga (Wrapper)
+ (void) hogeUsingBlockWrapper:(BlockHogeWrapper)block;
@end

@implementation HogeFuga (Wrapper)
+ (void) hogeUsingBlockWrapper:(BlockHogeWrapper)block;
{
[HogeFuga hogeUsingBlock:block];
}
@end


NSObject#descriptionをオーバーライドする

NSObject#descriptionのオーバーライドはメソッドをオーバーライドするかと思いきや、プロパティのオーバーライドになります。

class Hoge: NSObject {

var name: String?
var note: String?

override var description: String! {
get {
return "Name = \(self.name), Note = \(self.note)"
}
}
}

メソッドをオーバーライドしようするとエラーになります。

class Hoge: NSObject {

var name: String?
var note: String?

override func description() -> String {
return "Name = \(self.name), Note = \(self.note)"
}
}
// ERROR Method does not override any method from its superclass


参考


クロージャ型プロパティのオプショナルな書き方

クロージャ型を丸括弧で括ります。

class Fuga {

var completion: ( () -> () )?
}


ObjCのenum値をSwift記法で記載する方法がわからない

Swiftではenum値を記述するにはObjCと異りなり、enum型 ドット enum値の文法になります。

この記述


記述方法がわかりやすい例

enum型とPrefixに値を分割できます。

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {

UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,

}

// ObjC
UIViewAutoresizingFlexibleLeftMargin

// Swift

UIViewAutoresizing.FlexibleLeftMargin


記述方法の差異が少ない例

enum型と値のPrefixが一部(複数形のs)異なります。

typedef NS_OPTIONS(NSUInteger, UIViewAnimationOptions) {

UIViewAnimationOptionLayoutSubviews = 1 << 0,
UIViewAnimationOptionAllowUserInteraction = 1 << 1,

}

// ObjC
UIViewAnimationOptionLayoutSubviews

// Swift

UIViewAnimationOptions.LayoutSubviews


記述方法がわかりにくい例

enum型と値のPrefixがPrefixが一致していない場合です。

一致している部分のPrefixで分割しています。

typedef NS_ENUM(NSInteger, HogeStyle) {

HogeFugaDefault,
HogeFugaValue1,
HogeFugaValue2,
HogeFugaSubtitle
};

// ObjC
HogeFugaValue1

// Swift

HogeStyle.FugaValue1


enum型のメソッド引数や変数にenumで定義された定数以外を設定するとエラーになる

NSIntegerで定義されたenum型にNSInteger型の値(数値)を直接代入するとコンパイルエラーになります。

typedef NS_ENUM(NSInteger, UIViewContentMode) {

UIViewContentModeScaleToFill,
...
}

self.view.contentMode = 0
// => ERROR Cannot convert the expression's type '()' to type 'UIViewContentMode'


ObjCのClass型をパラメータにとるメソッドにSwiftのClassを渡す方法 (Restkit)

Class型をパラメータにとるメソッドにSwiftのClassを代入し、そのClassがObjC側でnewされるケースです。

@interface RKObjectMapping : RKMapping

{
// クラス型を引数にとる
+ (instancetype)mappingForClass:(Class)objectClass
}

...

@implementation RKObjectMappingOp
{
// 渡したクラス型でnewされる
return [mapping.objectClass new];
}

NSObject.Type型を代入すれば、うまくObjC側でもnewできました。

class HogeModel: NSObject {

}

var clazz: NSObject.Type = WTDHogeModel.self
let mapping: RKObjectMapping = RKObjectMapping(forClass: clazz)

以下のObjC由来のClassの取得方法で代入するとエラーになり,

うまくできませんでした。

// newするとERROR

var clazz: AnyClass = object_getClass(WTDHogeModel())

// ERROR => EXC_BAD_INSTRUNCTION
var clazz: AnyClass = NSClassFromString("WTDHogeModel")