@class_protocolと@objcの違いがいまいち理解出来なかったので調べてみました。
The Swift Programming Languageには、@class_protocolと@objcはクラス以外には採用できないとあるので確認してみます。
class_protocol
Apply this attribute to a protocol to indicate that the protocol can be adopted by class types only.
If you apply the objc attribute to a protocol, the class_protocol attribute is implicitly applied to that protocol; there’s no need to mark the protocol with the class_protocol attribute explicitly.
ref:The Swift Programming Language - Attributes
実装から違いを確認してみる
protocol
通常のprotocolを利用してClass、Struct、Enumに採用してみます。
Class、Struct、Enumに問題なくprotocolが採用できます。
import Foundation
protocol Prot {
func protFunc()
}
// classにprotocolを採用
class Impl : Prot {
func protFunc() {
println("Impl:Prot->protFunc protFunc")
}
}
// classにprotocolを採用
class Impl2 : Prot {
func protFunc() {
println("Impl2:Prot->protFunc protFunc")
}
}
// structにprotocolを採用
struct SomeStructure: Prot {
func protFunc() {
println("SomeStructure:Prot->protFunc protFunc")
}
}
// enumにprotocolを採用
enum SomeEnum: Prot {
case Off, On
func protFunc() {
println("SomeEnum:Prot->protFunc protFunc")
}
}
// それぞれのprotocolを指定した変数を代入するための構造体を定義
struct StructWithRef {
// 変数をweakで宣言するとシンタックスエラーとなる
// 'weak' cannot be applied to non-class type 'cProto1'
var cProto1 : Prot?
var cProto2 : Prot?
var strProt : Prot?
var enumProt : Prot?
}
func testFunc() {
var cimpl1 = Impl()
var cimpl2 = Impl2()
var simpl = SomeStructure()
var eimpl = SomeEnum.On
var structWithRef = StructWithRef(cProto1: cimpl1, cProto2: cimpl2, strProt: simpl ,enumProt:eimpl)
// Impl:Prot->protFunc protFunc が出力される
structWithRef.cProto1?.protFunc()
// Impl2:Prot->protFunc protFunc が出力される
structWithRef.cProto2?.protFunc()
// SomeStructure:Prot->protFunc protFunc が出力される
structWithRef.strProt?.protFunc()
// SomeEnum:Prot->protFunc protFunc が出力される
structWithRef.enumProt?.protFunc()
}
@class_protocol
@class_protocolをClass、Struct、Enumで試してみると、Class以外にはProtocolを採用できなとある通り Enum と Struct がシンタックスエラーになりました。
@class_protocolを採用した変数がweakで宣言出来てしまいますが、実行時に EXC_BAD_ACCESS が発生して実行時エラーになりました。
import Foundation
@class_protocol protocol ClaassProto {
func classProtoFunc()
}
// 問題ない
class ClassImpl : ClaassProto {
func classProtoFunc() {
println("ClassImpl:ClaassProto->func classProtoFunc")
}
}
class ClassImpl2 : ClaassProto {
func classProtoFunc() {
println("ClassImpl2:ClaassProto->func classProtoFunc")
}
}
// Structはシンタックスエラーになる
// Non-class type 'SomeStructure' cannot conform to class protocol 'ClaassProto'
struct SomeStructure: ClaassProto {
func classProtoFunc() {
println("SomeStructure:ClaassProto->func classProtoFunc")
}
}
// Enumもシンタックスエラーになる
// Non-class type 'SomeEnum' cannot conform to class protocol 'ClaassProto'
enum SomeEnum: ClaassProto {
func classProtoFunc() {
println("SomeEnum:ClaassProto->func classProtoFunc")
}
}
struct StructWithRef {
var classProto1 : ClaassProto?
var classProto2 : ClaassProto?
// なぜかweakで変数を定義できてしまう
weak var wclassProto : ClaassProto?
}
func testFunc() {
var cimpl1 = ClassImpl()
var cimpl2 = ClassImpl2()
var structWithRef = StructWithRef(classProto1: cimpl1, classProto2: cimpl2, wclassProto: cimpl1)
// ClassImpl:ClaassProto->func classProtoFunc が出力される
structWithRef.classProto1?.classProtoFunc()
// ClassImpl2:ClaassProto->func classProtoFunc が出力される
structWithRef.classProto2?.classProtoFunc()
// EXC_BAD_ACCESS になり実行時エラーになる
structWithRef.wclassProto?.classProtoFunc()
}
@objc
@class_protocolと同様に、Class以外にはProtocolを採用できなとある通り Enum と Struct がシンタックスエラーになりました。
@class_protocolと違い weak で定義した変数にプロトコルを採用が可能だし、実行時エラーも発生せずに実行が可能でした。
import Foundation
@objc protocol ObjcProto {
func objcProtoFunc()
}
// 問題ない
class ObjcImpl : ObjcProto {
func objcProtoFunc() {
println("ObjcImpl:ObjcProto->func objcProtoFunc")
}
}
class ObjcImpl2 : ObjcProto {
func objcProtoFunc() {
println("ObjcImpl:ObjcProto->func objcProtoFunc")
}
}
// Structはシンタックスエラーになる
// Non-class type 'SomeStructure' cannot conform to class protocol 'ObjcProto'
struct SomeStructure: ObjcProto {
func objcProtoFunc() {
println("SomeStructure:ObjcProto->func objcProtoFunc")
}
}
// Enumもシンタックスエラーになる
// Non-class type 'SomeEnum' cannot conform to class protocol 'ObjcProto'
enum SomeEnum: ObjcProto {
func objcProtoFunc() {
println("SomeEnum:ObjcProto->func objcProtoFunc")
}
}
struct StructWithRef {
var objcProto1 : ObjcProto?
var objcProto2 : ObjcProto?
// weakで変数を定義可能
weak var wobjcProto : ObjcProto?
}
func testFunc() {
var cimpl1 = ObjcImpl()
var cimpl2 = ObjcImpl2()
var structWithRef = StructWithRef(objcProto1: cimpl1, objcProto2: cimpl2, wobjcProto: cimpl1)
// ClassImpl:ClaassProto->func classProtoFunc が出力される
structWithRef.objcProto1?.objcProtoFunc()
// ClassImpl2:ClaassProto->func classProtoFunc が出力される
structWithRef.objcProto2?.objcProtoFunc()
// ObjcImpl:ObjcProto->func objcProtoFunc が出力される
structWithRef.wobjcProto?.objcProtoFunc()
}
testFunc()
LLVM的な違いを見てみる
事前準備
Protocolの定義のみを書いた状態で、中間言語を生成してどのようになっているのか確認してみます。
swiftをコマンドラインから実行できるように、事前にxcrunのパスを通しておきます。
export PATH=/Applications/Xcode6-Beta3.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:$PATH
確認には、下記のようなコマンドを利用しています。
xcrun swift -emit-ir objc_protocol.swift > objc_protocol_ir.txt
プロトコルの宣言における差異
通常のprotcol、@class_protocol、@objc の宣言については、下記のような結果になります。
%swift.protocol = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i32, i32 }
@0 = private unnamed_addr constant [24 x i8] c"P14swift_protocol4Prot_\00"
@_TMp14swift_protocol4Prot = constant %swift.protocol { i8* null, i8* getelementptr inbounds ([24 x i8]* @0, i64 0, i64 0), i8* null, i8* null, i8* null, i8* null, i8* null, i8* null, i32 72, i32 7 }
%swift.protocol = type { i8*, i8*, i8*, i8*, i8*, i8*, i8*, i8*, i32, i32 }
@0 = private unnamed_addr constant [32 x i8] c"P14class_protocol11ClaassProto_\00"
@_TMp14class_protocol11ClaassProto = constant %swift.protocol { i8* null, i8* getelementptr inbounds ([32 x i8]* @0, i64 0, i64 0), i8* null, i8* null, i8* null, i8* null, i8* null, i8* null, i32 72, i32 5 }
@"\01L_selector_data(objcProtoFunc)" = internal constant [14 x i8] c"objcProtoFunc\00", section "__TEXT,__objc_methname,cstring_literals", align 1
@0 = private unnamed_addr constant [8 x i8] c"v16@0:8\00"
@1 = private unnamed_addr constant [31 x i8] c"_TtP13objc_protocol9ObjcProto_\00"
@_PROTOCOL_INSTANCE_METHODS__TtP13objc_protocol9ObjcProto_ = private constant { i32, i32, [1 x { i8*, i8*, i8* }] } { i32 24, i32 1, [1 x { i8*, i8*, i8* }] [{ i8*, i8*, i8* } { i8* getelementptr inbounds ([14 x i8]* @"\01L_selector_data(objcProtoFunc)", i64 0, i64 0), i8* getelementptr inbounds ([8 x i8]* @0, i64 0, i64 0), i8* null }] }, section "__DATA, __objc_const", align 8
@_PROTOCOL_METHOD_TYPES__TtP13objc_protocol9ObjcProto_ = private constant { [1 x i8*] } { [1 x i8*] [i8* getelementptr inbounds ([8 x i8]* @0, i64 0, i64 0)] }, section "__DATA, __objc_const", align 8
@_PROTOCOL__TtP13objc_protocol9ObjcProto_ = private constant { i8*, i8*, i8*, { i32, i32, [1 x { i8*, i8*, i8* }] }*, i8*, i8*, i8*, i8*, i32, i32, { [1 x i8*] }* } { i8* null, i8* getelementptr inbounds ([31 x i8]* @1, i64 0, i64 0), i8* null, { i32, i32, [1 x { i8*, i8*, i8* }] }* @_PROTOCOL_INSTANCE_METHODS__TtP13objc_protocol9ObjcProto_, i8* null, i8* null, i8* null, i8* null, i32 80, i32 1, { [1 x i8*] }* @_PROTOCOL_METHOD_TYPES__TtP13objc_protocol9ObjcProto_ }, section "__DATA, __objc_const", align 8
@"\01l_OBJC_LABEL_PROTOCOL_$__TtP13objc_protocol9ObjcProto_" = weak hidden global i8* bitcast ({ i8*, i8*, i8*, { i32, i32, [1 x { i8*, i8*, i8* }] }*, i8*, i8*, i8*, i8*, i32, i32, { [1 x i8*] }* }* @_PROTOCOL__TtP13objc_protocol9ObjcProto_ to i8*), section "__DATA,__objc_protolist,coalesced,no_dead_strip", align 8
@"\01l_OBJC_PROTOCOL_REFERENCE_$__TtP13objc_protocol9ObjcProto_" = weak hidden global i8* bitcast ({ i8*, i8*, i8*, { i32, i32, [1 x { i8*, i8*, i8* }] }*, i8*, i8*, i8*, i8*, i32, i32, { [1 x i8*] }* }* @_PROTOCOL__TtP13objc_protocol9ObjcProto_ to i8*), section "__DATA,__objc_protorefs,coalesced,no_dead_strip", align 8
宣言部分だけから中間言語を生成すると、通常のprotocolと@class_protocolの違いはほとんどないようですが、@objcの場合は大幅に違いがあることがわかります。
プロトコルの採用における差異
宣言だけだとそれほど差がないprotocolと@class_protocolですが、ClassにProtocolを採用すると少し差異が見られます。
それぞれ、Protocolの宣言とProtocolを採用
@_TWPC14class_protocol9ClassImplS_11ClaassProto = constant [1 x i8*] [i8* bitcast (void (%C14class_protocol9ClassImpl*, %swift.type*)* @_TTWC14class_protocol9ClassImplS_11ClaassProtoFS1_14classProtoFuncUS1___fQPS1_FT_T_ to i8*)], align 8
...(省略)
define void @_TTWC14class_protocol9ClassImplS_11ClaassProtoFS1_14classProtoFuncUS1___fQPS1_FT_T_(%C14class_protocol9ClassImpl*, %swift.type* %Self) {
entry:
%1 = getelementptr inbounds %C14class_protocol9ClassImpl* %0, i32 0, i32 0, i32 0
%.metadata = load %swift.type** %1, align 8
%2 = bitcast %swift.type* %.metadata to void (%C14class_protocol9ClassImpl*)**
%3 = getelementptr inbounds void (%C14class_protocol9ClassImpl*)** %2, i64 8
%4 = load void (%C14class_protocol9ClassImpl*)** %3, align 8
%5 = bitcast void (%C14class_protocol9ClassImpl*)* %4 to i8*
%6 = bitcast i8* %5 to void (%C14class_protocol9ClassImpl*)*
call void %6(%C14class_protocol9ClassImpl* %0)
ret void
}
@_TWPC14swift_protocol4ImplS_4Prot = constant [1 x i8*] [i8* bitcast (void (%C14swift_protocol4Impl**, %swift.type*)* @_TTWC14swift_protocol4ImplS_4ProtFS1_8protFuncUS1___fRQPS1_FT_T_ to i8*)], align 8
...(省略)
define void @_TTWC14swift_protocol4ImplS_4ProtFS1_8protFuncUS1___fRQPS1_FT_T_(%C14swift_protocol4Impl** noalias, %swift.type* %Self) {
entry:
%1 = load %C14swift_protocol4Impl** %0, align 8
%2 = bitcast %C14swift_protocol4Impl* %1 to %swift.refcounted*
call void @swift_retain_noresult(%swift.refcounted* %2) #0
%3 = getelementptr inbounds %C14swift_protocol4Impl* %1, i32 0, i32 0, i32 0
%.metadata = load %swift.type** %3, align 8
%4 = bitcast %swift.type* %.metadata to void (%C14swift_protocol4Impl*)**
%5 = getelementptr inbounds void (%C14swift_protocol4Impl*)** %4, i64 8
%6 = load void (%C14swift_protocol4Impl*)** %5, align 8
%7 = bitcast void (%C14swift_protocol4Impl*)* %6 to i8*
%8 = bitcast i8* %7 to void (%C14swift_protocol4Impl*)*
call void %8(%C14swift_protocol4Impl* %1)
ret void
}
感想
@class_protocolと@objcはクラス以外には採用できないので、クラス以外に採用する際には通常のrotocolを利用する必要があることが確認できました。
LLVMてきにみると、protocolと@class_protocolは宣言ではほとんど差がないのは意外でした。