しかし、Swiftをどれだけよく知っていますか? この記事では、Swiftインタビューのサンプル質問を見つけます。
これらの質問を使用して、候補者にインタビューして知識をテストしたり、候補者をテストしたりできます。 答えがわからなくても心配しないでください。各質問には答えがあります。
質問は3つのグループに分けられます。
- 初心者 :初心者向け。 数冊の本を読んで、自分のアプリケーションでSwiftを適用しました。
- 中級 :言語に本当に興味がある人に適しています。 あなたはすでにそれについて多くを読んでおり、しばしば実験します。
- 高度 :最も高度な開発者に適しています-構文のジャングルに入り込み、高度なテクニックを使用したい人。
各レベルには2種類の質問があります。
- written :コードの作成を提案しているため、電子メールによるテストに適しています。
- 口頭 :言葉で十分な答えがあるので、電話または人で話すときに使用できます。
この記事を読みながら、質問のコードを確認できるようにプレイグラウンドを開いたままにしてください。 すべての回答は、 Xcode 10.2およびSwift 5でテストされています。
初心者
書かれた質問質問1次のコードを検討してください。
struct Tutorial { var difficulty: Int = 1 } var tutorial1 = Tutorial() var tutorial2 = tutorial1 tutorial2.difficulty = 2
tutorial1.difficultyおよびtutorial2.difficultyの値は何ですか? チュートリアルがクラスだった場合、違いはありますか? なんで?
答えtutorial1.difficultyは1、tutorial2.difficultyは2です。
Swiftでは、構造は値型です。 それらはコピーされ、参照されません。 次の行は、 tutorial1をコピーし、 tutorial2に割り当てます。
var tutorial2 = tutorial1
tutorial2の変更は、 tutorial1には影響しません。
Tutorialがクラスの場合、 tutorial1.difficultyとtutorial2.difficultyは2になります。Swiftのクラスは参照型です。 tutorial1プロパティを変更すると、tutorial2にも同じ変更が表示されます。逆も同様です。
質問2varでview1を宣言し、 letでview2を宣言しました 。 違いは何ですか、最後の行はコンパイルされますか?
import UIKit var view1 = UIView() view1.alpha = 0.5 let view2 = UIView() view2.alpha = 0.5 // ?
答えはい、最後の行がコンパイルされます。 view1は変数であり、その値をUIViewの新しいインスタンスに割り当てることができます。 letを使用すると、値を1回しか割り当てることができないため、次のコードはコンパイルされません。
view2 = view1 // : view2 is immutable
ただし、UIViewは参照セマンティクスを持つクラスであるため、view2のプロパティを変更できます 。つまり、コードがコンパイルされます 。
質問3このコードは、配列をアルファベット順に並べ替えます。 可能な限り閉鎖を簡素化します。
var animals = ["fish", "cat", "chicken", "dog"] animals.sort { (one: String, two: String) -> Bool in return one < two } print(animals)
答えSwiftはクロージャーパラメーターのタイプと戻り値のタイプを自動的に決定するため、 これらを削除できます 。
animals.sort { (one, two) in return one < two }
$ i表記を使用してパラメーター名を置き換えることができます。
animals.sort { return $0 < $1 }
単一のステートメントで構成されるクロージャーには、 returnキーワードを含めることはできません。 最後に実行されたステートメントの値は、クロージャーの戻り結果になります。
animals.sort { $0 < $1 }
最後に、Swiftは配列の要素がEquatableプロトコルに準拠していることを知っているため、 次のように書くことができます。
animals.sort(by: <)
UPD: hummingbirddjの簡素化:
この場合、さらに短くすることができます:
animals.sort()
質問4このコードは、 AddressとPersonの 2つのクラスを作成します。 Personクラスの2つのインスタンス( RayとBrian )も作成されます。
class Address { var fullAddress: String var city: String init(fullAddress: String, city: String) { self.fullAddress = fullAddress self.city = city } } class Person { var name: String var address: Address init(name: String, address: Address) { self.name = name self.address = address } } var headquarters = Address(fullAddress: "123 Tutorial Street", city: "Appletown") var ray = Person(name: "Ray", address: headquarters) var brian = Person(name: "Brian", address: headquarters)
Brianが新しい住所に移動し、次のようにレコードを更新するとします。
brian.address.fullAddress = "148 Tutorial Street"
これはコンパイルされ、エラーなしで実行されます。 しかし、ここでRayのアドレスを確認すると、彼も「動いた」ことがわかります。
ここで何が起こったのですか、どうすれば修正できますか?
答えアドレスはクラスであり、 参照セマンティクスを持っています 。 したがって、 本部は、レイとブライアンが共有するクラスの同じインスタンスです。 本社を変更すると、両方の住所が変更されます。
これを修正するには、Addressクラスの新しいインスタンスを作成してBrianに割り当てるか、Addressをclass ではなく構造体として宣言します。
口頭質問質問1オプションは何で、どのような問題を解決しますか?
答えoptionalを使用すると、任意のタイプの変数が「 値なし 」状態を提示できます 。 Objective-Cでは、「値なし」は特別なnil値を使用する参照型でのみ使用できました。 intやfloatなどの値型には、この機能がありませんでした。
Swiftは、「値なし」の概念を値型に拡張しました。 オプションの変数には値またはnilを含めることができ、値がないことを示します。
質問2構造とクラスの主な違いを簡単にリストします。
答えクラスは継承をサポートしますが、構造体はサポートしません。
クラスは参照型であり、構造体は値型です。
質問3ジェネリックとは何ですか?
答えSwiftでは、クラス、構造、および列挙でジェネリックを使用できます。
ジェネリック修正コードの重複 あるタイプのパラメーターを受け入れるメソッドがある場合、別のタイプのパラメーターを操作するためにコードを複製する必要がある場合があります。
たとえば、このコードでは、2番目の関数は最初の関数の「クローン」ですが、整数ではなく文字列パラメーターがあります。
func areIntEqual(_ x: Int, _ y: Int) -> Bool { return x == y } func areStringsEqual(_ x: String, _ y: String) -> Bool { return x == y } areStringsEqual("ray", "ray") // true areIntEqual(1, 1) // true
ジェネリックを使用して、2つの関数を1つに組み合わせ、同時に型の安全性を確保します。
func areTheyEqual<T: Equatable>(_ x: T, _ y: T) -> Bool { return x == y } areTheyEqual("ray", "ray") areTheyEqual(1, 1)
同等性をテストしているため、型をEquatableプロトコルに準拠する型に制限しています。 このコードは望ましい結果を提供し、間違ったタイプのパラメーターの転送を防ぎます。
質問4場合によっては、暗黙的にラップされていないオプションを避けることができません。 いつ、なぜ?
答え暗黙的にラップされていないオプションを使用する最も一般的な理由:
- 作成時にnilでないプロパティを初期化できない場合。 典型的な例は、Interface Builderのアウトレットです。これは、所有者の後に常に初期化されます。 この特別な場合、Interface Builderですべてが正しく設定されていれば、使用する前にアウトレットが非nilであることが保証されます。
- クラスの2つのインスタンスが相互に参照し、別のインスタンスへの非ゼロ参照が必要な場合の強参照ループの問題を解決します。 この場合、一方の側でリンクをunownedとしてマークし、反対側で暗黙的なオプションの拡張を使用します。
質問5オプションで展開するにはどうすればよいですか? セキュリティの観点から評価してください。
var x : String? = "Test"
ヒント:たった7つの方法。
答え強制的なアンラッピングは安全ではありません。
let a: String = x!
変数を宣言するときの暗黙的な展開は安全ではありません。
var a = x!
オプションのバインディングは安全です。
if let a = x { print("x was successfully unwrapped and is = \(a)") }
オプションの連鎖は安全です。
let a = x?.count
ゼロの合体演算子は安全です。
let a = x ?? ""
Guardステートメントは安全です。
guard let a = x else { return }
オプションのパターン -安全。
if case let a? = x { print(a) }
中級
書かれた質問質問1nilと.noneの違いは何ですか?
答え違いはありません。Optional.none (簡潔には.none )とnilは同等です。
実際、次のステートメントはtrueを返します 。
nil == .none
nilの使用はより一般的に受け入れられ推奨されています。
質問2これは、クラスと構造の形式の温度計モデルです。 コンパイラは最後の行について文句を言います。 そこで何が問題なのですか?
public class ThermometerClass { private(set) var temperature: Double = 0.0 public func registerTemperature(_ temperature: Double) { self.temperature = temperature } } let thermometerClass = ThermometerClass() thermometerClass.registerTemperature(56.0) public struct ThermometerStruct { private(set) var temperature: Double = 0.0 public mutating func registerTemperature(_ temperature: Double) { self.temperature = temperature } } let thermometerStruct = ThermometerStruct() thermometerStruct.registerTemperature(56.0)
答えThermometerStructは 、内部変数を変更する変更関数で正しく宣言されています。 コンパイラは、 letで作成されたインスタンスのregisterTemperatureメソッドを呼び出していると不平を言っているため、このインスタンスは不変です。 letをvarに変更すると、コンパイルエラーが修正されます。
構造体では、内部変数を変更するメソッドをmutatingとしてマークする必要がありますが、不変インスタンスを使用してこれらのメソッドを呼び出すことはできません。
質問3このコードは何を出力し、その理由は何ですか?
var thing = "cars" let closure = { [thing] in print("I love \(thing)") } thing = "airplanes" closure()
答えそれは印刷されます:私は車が大好きです。 キャプチャリストは、クロージャが宣言されたときに変数のコピーを作成します。 これは、新しい値を割り当てた後でも、キャプチャされた変数がその値を変更しないことを意味します。
クロージャでキャプチャリストを省略すると、コンパイラはコピーではなくリンクを使用します。 クロージャー呼び出しは変数の変更を反映します:
var thing = "cars" let closure = { print("I love \(thing)") } thing = "airplanes" closure() // Prints: "I love airplanes"
質問4これは、配列内の一意の値の数をカウントする関数です。
func countUniques<T: Comparable>(_ array: Array<T>) -> Int { let sorted = array.sorted() let initial: (T?, Int) = (.none, 0) let reduced = sorted.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) } return reduced.1 }
ソートを使用するため、Comparableプロトコルに準拠するタイプのみを使用します。
次のように呼び出すことができます。
countUniques([1, 2, 3, 3]) // 3
この関数をArray拡張として書き換えて、次のように使用できるようにします。
[1, 2, 3, 3].countUniques() // 3
翻訳者のメモ痛みを伴うものは巨大な機能です。 なぜ:
func countUniques<T: Hashable>(_ array: Array<T>) -> Int { return Set(array).count }
答えextension Array where Element: Comparable { func countUniques() -> Int { let sortedValues = sorted() let initial: (Element?, Int) = (.none, 0) let reduced = sortedValues.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) } return reduced.1 } }
質問5これは、2つのオプションのdoubleを分割する関数です。 満たす必要がある3つの条件があります。
- 配当はゼロであってはなりません
- 仕切りはゼロであってはなりません
- 除数は0であってはなりません
func divide(_ dividend: Double?, by divisor: Double?) -> Double? { if dividend == nil { return nil } if divisor == nil { return nil } if divisor == 0 { return nil } return dividend! / divisor! }
guard関数を使用して、強制的なアンラップを使用せずにこの関数を書き換えます。
答えSwift 2.0で導入されたガードステートメントは、条件が満たされない場合に終了を提供します。 以下に例を示します。
guard dividend != nil else { return nil }
オプションのバインディングにガードステートメントを使用することもできます 。その後、展開された変数はガードステートメントの外側で利用可能になります。
guard let dividend = dividend else { return .none }
したがって、次のように関数を書き換えることができます。
func divide(_ dividend: Double?, by divisor: Double?) -> Double? { guard let dividend = dividend else { return nil } guard let divisor = divisor else { return nil } guard divisor != 0 else { return nil } return dividend / divisor }
被除数と除数は既にアンパックされており、それらをオプションではない不変変数に入れているため、強制的なアンパックがないことに注意してください。
また、guardステートメント内のアンパックされたオプションの結果は、guardステートメント外でも利用できることに注意してください。
ガードステートメントをグループ化することにより、関数をさらに簡素化できます。
func divide(_ dividend: Double?, by divisor: Double?) -> Double? { guard let dividend = dividend, let divisor = divisor, divisor != 0 else { return nil } return dividend / divisor }
質問6if letステートメントを使用して、質問5のメソッドを書き換えます。
答えif letステートメントを使用すると、オプションをアンパックし、このコードブロック内でこの値を使用できます。 それ以外では、これらの値は使用できません。
func divide(_ dividend: Double?, by divisor: Double?) -> Double? { if let dividend = dividend, let divisor = divisor, divisor != 0 { return dividend / divisor } else { return nil } }
口頭質問質問1Objective-Cでは、次のような定数を宣言します。
const int number = 0;
したがって、Swiftでは:
let number = 0
違いは何ですか?
答えObjective-Cでは、 コンパイル時に定数はこの時点で既知の値で初期化されます 。
letで作成される不変の値は、 実行時に定義される定数です。 静的または動的な式で初期化できます。 したがって、これを行うことができます。
let higherNumber = number + 5
このような割り当ては1回しか実行できないことに注意してください。
質問2値型の静的プロパティまたは関数を宣言するには、 static修飾子が使用されます。 構造の例を次に示します。
struct Sun { static func illuminate() {} }
また、クラスの場合は、 静的修飾子またはクラス修飾子を使用できます 。 結果は同じですが、違いがあります。 説明してください。
答えstaticは、プロパティまたは関数を静的で重複しないようにします。 クラスを使用すると、プロパティまたは関数がオーバーライドされます 。
ここで、コンパイラーは、 illuminate()をブロックしようと誓います:
class Star { class func spin() {} static func illuminate() {} } class Sun : Star { override class func spin() { super.spin() } // error: class method overrides a 'final' class method override static func illuminate() { super.illuminate() } }
質問3拡張機能を使用して、 保存されたプロパティを型に追加することはできますか? どうしてですか?
答えいいえ、これは不可能です。 拡張機能を使用して既存の型に新しい動作を追加できますが、型自体またはそのインターフェイスを変更することはできません。 新しい保存されたプロパティを保存するには、追加のメモリが必要であり、拡張機能はこれを行うことができません。
質問4Swiftのプロトコルとは何ですか?
答えプロトコルは、メソッド、プロパティなどの概要を定義するタイプです。 クラス、構造体、または列挙型は、プロトコルを使用してこれらすべてを実装できます。 プロトコル自体は機能を実装していませんが、定義しています。
高度な
書かれた質問質問1温度計のモデルを定義する構造があるとします:
public struct Thermometer { public var temperature: Double public init(temperature: Double) { self.temperature = temperature } }
インスタンスを作成するには、次のように記述します。
var t: Thermometer = Thermometer(temperature:56.8)
しかし、このようなもののほうがはるかに便利です。
var thermometer: Thermometer = 56.8
これは可能ですか? どうやって?
答えSwiftは、割り当てによってリテラルを使用して型を初期化できるプロトコルを定義します。 適切なプロトコルを適用し、パブリック初期化子を提供すると、リテラルによる初期化が可能になります。 温度計の場合、 ExpressibleByFloatLiteralを実装します。
extension Thermometer: ExpressibleByFloatLiteral { public init(floatLiteral value: FloatLiteralType) { self.init(temperature: value) } }
これで、次のようなインスタンスを作成できます。
var thermometer: Thermometer = 56.8
質問2Swiftには、算術および論理演算用の事前定義された演算子のセットがあります。 また、単項演算子と二項演算子の両方の独自の演算子を作成できます。
以下の要件に従って、独自のべき乗演算子(^^)を定義および実装します。
- パラメーターとして2つのintを取ります
- 最初のパラメーターを2番目のパラメーターで累乗した結果を返します
- 代数演算の順序を正しく処理します
- 考えられるオーバーフローエラーを無視します
答え新しいオペレーターの作成は、アナウンスと実装の2段階で行われます。
宣言では、 演算子キーワードを使用して型(単項またはバイナリ)を指定し、新しい演算子の文字シーケンス、その結合性、および実行の優先順位を指定します。
ここでは、演算子は^^で、そのタイプは中置(バイナリ)です。 結合性は正しいです。
Swiftには、累乗の定義済みの年功はありません。 代数では、乗算/除算の前にべき乗を計算する必要があります。 このようにして、乗法よりも高いべき乗を配置することにより、カスタムの実行順序を作成します。
この発表:
precedencegroup ExponentPrecedence { higherThan: MultiplicationPrecedence associativity: right } infix operator ^^: ExponentPrecedence
これは実装です:
func ^^(base: Int, exponent: Int) -> Int { let l = Double(base) let r = Double(exponent) let p = pow(l, r) return Int(p) }
質問3次のコードは、 ピザの構造とmakeMargherita()メソッドのデフォルト実装の拡張機能を備えたPizzeriaプロトコルを定義しています。
struct Pizza { let ingredients: [String] } protocol Pizzeria { func makePizza(_ ingredients: [String]) -> Pizza func makeMargherita() -> Pizza } extension Pizzeria { func makeMargherita() -> Pizza { return makePizza(["tomato", "mozzarella"]) } }
次に、 Lombardisレストランを定義します。
struct Lombardis: Pizzeria { func makePizza(_ ingredients: [String]) -> Pizza { return Pizza(ingredients: ingredients) } func makeMargherita() -> Pizza { return makePizza(["tomato", "basil", "mozzarella"]) } }
次のコードは、 Lombardisの 2つのインスタンスを作成します。 どれがバジルでマルガリータを作りますか?
let lombardis1: Pizzeria = Lombardis() let lombardis2: Lombardis = Lombardis() lombardis1.makeMargherita() lombardis2.makeMargherita()
答え両方で。 PizzeriaプロトコルはmakeMargherita()メソッドを宣言し、デフォルトの実装を提供します。 Lombardisの実装は、デフォルトのメソッドをオーバーライドします。 プロトコルでメソッドを2つの場所で宣言したため、正しい実装が呼び出されます。
しかし、プロトコルがmakeMargherita()メソッドを宣言せず、拡張機能が次のようにデフォルトの実装を提供する場合はどうなるでしょうか:
protocol Pizzeria { func makePizza(_ ingredients: [String]) -> Pizza } extension Pizzeria { func makeMargherita() -> Pizza { return makePizza(["tomato", "mozzarella"]) } }
この場合、拡張で定義されたメソッドを使用するため、lombardis2のみがバジル付きのピザを持ち、lombardis1はピザを持ちません。
質問4次のコードはコンパイルされません。 彼の何が悪いのか説明できますか? 問題の解決策を提案してください。
struct Kitten { } func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") } print(k) }
ヒント:エラーを修正するには3つの方法があります。
答えguardステートメントのelseブロックには、 returnを使用するか、例外をスローするか、 @noreturnを呼び出すexitオプションが必要です 。 最も簡単な解決策は、 returnステートメントを追加することです。
func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") return } print(k) }
このソリューションは例外をスローします:
enum KittenError: Error { case NoKitten } struct Kitten { } func showKitten(kitten: Kitten?) throws { guard let k = kitten else { print("There is no kitten") throw KittenError.NoKitten } print(k) } try showKitten(kitten: nil)
最後に、 @noreturn関数であるfatalError()の呼び出しを示します。
struct Kitten { } func showKitten(kitten: Kitten?) { guard let k = kitten else { print("There is no kitten") fatalError() } print(k) }
口頭質問質問1クロージャは参照型ですか、それとも値型ですか?
答えクロージャーは参照タイプです。 クロージャーを変数に割り当ててから別の変数にコピーする場合、同じクロージャーとそのキャプチャリストへのリンクをコピーします。
質問2UInt型を使用して、符号なし整数を格納します。 符号付きの全体から変換するための初期化子を実装します。
init(_ value: Int)
ただし、負の値を指定すると、次のコードはコンパイルされません。
let myNegative = UInt(-1)
定義により、符号付き整数は負にできません。 ただし、メモリ内の負の数の表現を使用して、符号なしの数に変換することは可能です。
負の整数をメモリに表現したままUIntに変換するにはどうすればよいですか?
答えこれにはイニシャライザーがあります:
UInt(bitPattern: Int)
そして使用:
let myNegative = UInt(bitPattern: -1)
質問3Swiftで循環参照を説明しますか? どうすれば修正できますか?
答え循環参照は、2つのインスタンスが相互に強い参照を含む場合に発生し、これらのインスタンスはいずれも解放できないため、メモリリークが発生します。 インスタンスはまだ強い参照が存在する間は解放できませんが、一方のインスタンスは他方を保持します。
これは、キーワードweakまたはunownedを指定して、一方のリンクを置き換えることで解決できます。
質問4Swiftでは、再帰的な列挙を作成できます。 以下は、2つの連想型TとListを持つNodeバリアントを含む列挙の例です。
enum List<T> { case node(T, List<T>) }
コンパイルエラーが発生します。 私たちは何を見逃しましたか?
答え同様の再帰的な列挙オプションを許可する間接キーワードを忘れました:
enum List<T> { indirect case node(T, List<T>) }