Swift
Swift2.2
Swift3.0
Xcode8

Swift2.2 から Swift3.0 への変換実例メモ

More than 1 year has passed since last update.

Swift 2.2 から Swift 3.0 に自動変換した場合に、うまくいった場合と手動で直さなければいけなかった場合のまとめ。自分のプロジェクト内で、実際に発生した内容をメモにのしていたものです。生々しい名前は編集してあります。また、メモを取る時に操作ミスなどが混在している場合もあるので、ご留意くださいませ。

2017.12 加筆†


Foundation

// before

private let _gregorian = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!

// after

private let _gregorian = Calendar(identifier: Calendar.Identifier.gregorian)

NSCalendar(calendarIdentifier:) のイニシャライザは nil を返す可能性があるが、Calendar(identifier:) はSwiftの文法的に不正な値を渡せなくなり、戻り値はオプショナルではない。


// before

let defaults = NSUserDefaults.standardUserDefaults()
if let data = defaults.valueForKey("peerID") as? NSData {
}

// after

let defaults = UserDefaults.standard
if let data = defaults.value(forKey: "peerID") as? Data {
}

standardUserDefaults()standard


// before

let scanner = NSScanner(string: item)

// after

let scanner = Scanner(string: item)

NSScanner -> Scanner



Core Graphics

// before

return CGSizeMake(self.cornerRadius, self.cornerRadius)

// after

return CGSize(width: self.cornerRadius, height: self.cornerRadius)

CGSizeMake → CGSize


// before

var bounds = self.bounds
var bottomRect = CGRectZero
CGRectDivide(bounds, &bottomRect, &bounds, buttonHeight, .MaxYEdge)

// after

(bottomRect, bounds) = bounds.divided(atDistance: buttonHeight, from: .maxYEdge)

CGRectDivide → bounds.divided()bounds.divided()は Tuple を返す。自動変換はまず失敗。


// before

let context = UIGraphicsGetCurrentContext()
CGContextSetStrokeColorWithColor(context, UIColor(white: 0, alpha: 0.5).CGColor)

// after

let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(UIColor(white: 0, alpha: 0.5).cgColor)

CGContextSetStrokeColorWithColor() -> context.setStrokeColor()


// before

CGContextMoveToPoint(context, x, y)
CGContextAddLineToPoint(context, x, CGRectGetMaxY(self.bounds))
CGContextStrokePath(context)

// after

context.move(to: CGPoint(x: x, y: y))
context.addLine(to: CGPoint(x: x, y: (self.bounds).maxY))
context.strokePath()

CGContext はまるでオブジェクトかのような書き方が可能。


// before

extension CGRect {
var width: CGFloat { return CGRectGetWidth(self) }
var height: CGFloat { return CGRectGetHeight(self) }
}

// after

extension CGRect {
//var width: CGFloat { return self.width }
//var height: CGFloat { return self.height }
}

width, height は Swift 3 で用意されるようになりました。 width, height は以前からあった模様。Swift 3 でエラーになるようになりました。


// before

if let document = CGPDFDocumentCreateWithURL(NSURL.fileURLWithPath(fullpath)) {
}

// after

if let document = CGPDFDocument(URL(fileURLWithPath: fullpath) as NSURL) {
}


// before

let objects = array.reverse()

// after

let objects = array.reversed()


// before

class ZCache<T: AnyObject> {
private var cache = NSCache()
...
}

// after

/*
class ZCache<T: AnyObject> {
private var cache = NSCache()
...
}
*/

NSCache を Generics 見たいに使えるようにする NSCache のラッパークラスを開発したが、Swift 3 では NSCache 自体が Generics なので、ラッパーの存在意義がなくなった?


// before

if let hashValue = self.object { return unsafeAddressOf(object).hashValue }

// after

if let hashValue = self.object { return Unmanaged.passUnretained(object).toOpaque().hashValue }

なぜこうなったか…

2017.12 加筆†:多分この方がいいかな。

let hashValue = ObjectIdentifier(object).hashValue


// before

let value = object.integerValue
let integerLength = sizeof(value.dynamicType)

// after

let value = object.integerValue
let integerLength = MemoryLayout<UInt32>.size

integerValue の戻り値を将来にわたって、UInt32 4バイト である事を確認したいが、同様の書き方はむづかしそう。誰かが、object の integer の戻り値の型を変更しても、ランタイム時などに捕まえる事はむづかしそう。


// before

let data: NSData = ...
let string = String(bytes: data.bytes, length: data.length, encoding: NSWindowsCP1252StringEncoding)

// after

let data: Data = ...
let string = String(data: data, encoding: String.Encoding.windowsCP1252)

data.bytes → data そのまま渡す。type


// before

CGPathAddArcToPoint(pathRef, nil, l, t, l, t+radius, radius)

// after

pathRef.addArc(tangent1End: CGPoint(x: l, y: t), tangent2End: CGPoint(x: l, y: t+radius), radius: radius)

x, yCGPoint になったり、tangent1End, tangent2End なんて名前がついたり…


// before

var isDir: ObjCBool = false
if NSFileManager.defaultManager().fileExistsAtPath(directory, isDirectory: &isDir) && isDir {
}

// after

var isDir: ObjCBool = false
if FileManager.default.fileExists(atPath: directory, isDirectory: &isDir) && isDir.boolValue {
}

NSFileManager → FileManager, isDirisDir.boolValue


// before

let MyNotification = "MyNotification"

NSNotificationCenter.defaultCenter().postNotificationName(MyNotification, object: nil)

// after

extension Notification.Name {
static let redrawPageNotification = Notification.Name("MyNotification")
}

NotificationCenter.default.removeObserver(self, name: .redrawPageNotification, object: nil)

// alternative - auto

NotificationCenter.default.post(name: NSNotification.Name(rawValue: MyNotification), object: nil)


GCD

// before

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
}

// after

DispatchQueue.global(qos: .default).async {
}

GCD の変わり方も大胆です。


Core Data

// before

let request = NSFetchRequest(entityName: entityItem)
request.predicate = NSPredicate(format: "(url == %@) AND (feed == %@)", link, feed)
let objects = try! self..managedObjectContext.executeFetchRequest(request)
return objects.first as? RSSItem

// after

let request = NSFetchRequest<RSSItem>(entityName: entityItem)
request.predicate = NSPredicate(format: "(url == %@) AND (feed == %@)", link, feed)
let objects = try! self.managedObjectContext.fetch(request)
return objects.first

NSFetchRequest()NSFetchRequest<RSSItem>()、Core Data も Generics で扱えるようになりました。


AppKit

// before

let mask: NSEventModifierFlags = [.ShiftKeyMask, .ControlKeyMask, .AlternateKeyMask, .CommandKeyMask]

// after

let mask: NSEventModifierFlags = [.shift, .control, .option, .command]

// auto

let mask: NSEventModifierFlags = [.shift, .control, .AlternateKeyMask, .command]

自動変換で、AlternateKeyMaskはなぜか変換してくれません。


UIKit

// before

override func drawRect(rect: CGRect) {
}

// after

override func draw(_ rect: CGRect) {
}

drawRectdraw


// before

self.backgroundColor = UIColor.clearColor()
self.layer.shadowColor = UIColor.blackColor().CGColor

// after

self.backgroundColor = UIColor.clear
self.layer.shadowColor = UIColor.black.cgColor


// before

@IBAction func playAction(sender: AnyObject) {
}

// after

@IBAction func playAction(_ sender: AnyObject) {
}


// before

let myView: MyView? = myViews.filter { CGRectContainsPoint($0.bounds, gesture.locationInView($0)) }.first

// after

let myView: MyView? = myViews.filter { $0.bounds.contains(gesture.location(in: $0)) }.first


// before

self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier)

// after

self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier)

registerClass → register


// before

self.dismissViewControllerAnimated(true, completion: nil)

// after

self.dismiss(animated: true, completion: nil)

ViewControllerが消失。


// before

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
}

// after

override var supportedInterfaceOrientations : UIInterfaceOrientationMask {
}

メソッド(関数)から、プロパティへ


// before

currentViewController.willMoveToParentViewController(nil)

// after

currentViewController.willMove(toParentViewController: nil)

willMoveToParentViewController()willMove(toParentViewController:)


// before

override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
if let touches = touches {
for touch in touches {
}
}
}

// after

override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
}
}

Set<UITouch>?Set<UITouch> なので、optional binding が不要になった。


まとめ

必要があれば、順次追加していこうと思います。

Xcode Version 8.0 (8A218a)

Apple Swift version 3.0 (swiftlang-800.0.46.2 clang-800.0.38)