LoginSignup
22
23

More than 5 years have passed since last update.

protocolの@class_protocolと@objcの違いを調べてみた

Last updated at Posted at 2014-07-21

@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が採用できます。

swift_protocol.swift
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 が発生して実行時エラーになりました。

class_protocol.swift
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 で定義した変数にプロトコルを採用が可能だし、実行時エラーも発生せずに実行が可能でした。

objc_protocol.swift
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_ir.txt(抜粋)
%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 }

class_protocol_ir.txt(抜粋)
%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 }
objc_protocol_ir.txt(抜粋)
@"\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を採用

class_protocol_ir.txt(抜粋)
@_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
}
swift_protocol_ir.txt(抜粋)
@_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は宣言ではほとんど差がないのは意外でした。

参考資料

22
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
23