Support interop modularity
* Add list of included headers into the manifest * Implement importing "foreign" type declarations * Implement forward declarations more natively in the compiler * Represent Objective-C categories as Kotlin extension methods and properties Also: * Do some refactoring in stub generation * Call bridges directly in stubs for Objective-C properties
This commit is contained in:
committed by
SvyatoslavScherbina
parent
82b2b76c09
commit
79455c5161
+58
-1
@@ -16,7 +16,12 @@
|
||||
|
||||
package org.jetbrains.kotlin.backend.konan
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.ClassifierAliasingPackageFragmentDescriptor
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.ExportedForwardDeclarationsPackageFragmentDescriptor
|
||||
import org.jetbrains.kotlin.backend.konan.library.KonanLibraryReader
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
@@ -25,6 +30,23 @@ import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
interface InteropLibrary {
|
||||
fun createSyntheticPackages(
|
||||
module: ModuleDescriptor,
|
||||
kotlinPackageFragments: List<PackageFragmentDescriptor>
|
||||
): List<PackageFragmentDescriptor>
|
||||
}
|
||||
|
||||
fun createInteropLibrary(reader: KonanLibraryReader): InteropLibrary? {
|
||||
val pkg = reader.manifestProperties.getProperty("pkg") ?: return null
|
||||
val exportForwardDeclarations = reader.manifestProperties
|
||||
.getProperty("exportForwardDeclarations").split(' ')
|
||||
.map { it.trim() }.filter { it.isNotEmpty() }
|
||||
.map { FqName(it) }
|
||||
|
||||
return InteropLibraryImpl(FqName(pkg), exportForwardDeclarations)
|
||||
}
|
||||
|
||||
private val cPointerName = "CPointer"
|
||||
private val nativePointedName = "NativePointed"
|
||||
|
||||
@@ -35,6 +57,13 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
|
||||
|
||||
val cPointer = packageName.child(Name.identifier(cPointerName)).toUnsafe()
|
||||
val nativePointed = packageName.child(Name.identifier(nativePointedName)).toUnsafe()
|
||||
|
||||
val cNames = FqName("cnames")
|
||||
val cNamesStructs = cNames.child(Name.identifier("structs"))
|
||||
|
||||
val objCNames = FqName("objcnames")
|
||||
val objCNamesClasses = objCNames.child(Name.identifier("classes"))
|
||||
val objCNamesProtocols = objCNames.child(Name.identifier("protocols"))
|
||||
}
|
||||
|
||||
private val packageScope = builtIns.builtInsModule.getPackage(FqNames.packageName).memberScope
|
||||
@@ -174,4 +203,32 @@ private fun MemberScope.getContributedClass(name: String): ClassDescriptor =
|
||||
this.getContributedClassifier(Name.identifier(name), NoLookupLocation.FROM_BUILTINS) as ClassDescriptor
|
||||
|
||||
private fun MemberScope.getContributedFunctions(name: String) =
|
||||
this.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BUILTINS)
|
||||
this.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BUILTINS)
|
||||
|
||||
private class InteropLibraryImpl(
|
||||
private val packageFqName: FqName,
|
||||
private val exportForwardDeclarations: List<FqName>
|
||||
) : InteropLibrary {
|
||||
override fun createSyntheticPackages(
|
||||
module: ModuleDescriptor,
|
||||
kotlinPackageFragments: List<PackageFragmentDescriptor>
|
||||
): List<PackageFragmentDescriptor> {
|
||||
val interopPackageFragments = kotlinPackageFragments.filter { it.fqName == packageFqName }
|
||||
|
||||
val fqNames = InteropBuiltIns.FqNames
|
||||
|
||||
val result = mutableListOf<PackageFragmentDescriptor>()
|
||||
|
||||
// Allow references to forwarding declarations to be resolved into classifiers declared in this library:
|
||||
listOf(fqNames.cNamesStructs, fqNames.objCNamesClasses, fqNames.objCNamesProtocols).mapTo(result) { fqName ->
|
||||
ClassifierAliasingPackageFragmentDescriptor(interopPackageFragments, module, fqName)
|
||||
}
|
||||
// TODO: use separate namespaces for structs, enums, Objective-C protocols etc.
|
||||
|
||||
result.add(ExportedForwardDeclarationsPackageFragmentDescriptor(
|
||||
module, packageFqName, exportForwardDeclarations
|
||||
))
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
+20
-1
@@ -17,12 +17,14 @@
|
||||
package org.jetbrains.kotlin.backend.konan
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.createForwardDeclarationsModule
|
||||
import org.jetbrains.kotlin.backend.konan.library.KonanLibraryReader
|
||||
import org.jetbrains.kotlin.backend.konan.library.KonanLibrarySearchPathResolver
|
||||
import org.jetbrains.kotlin.backend.konan.library.impl.LibraryReaderImpl
|
||||
import org.jetbrains.kotlin.backend.konan.util.profile
|
||||
import org.jetbrains.kotlin.backend.konan.util.removeSuffixIfPresent
|
||||
import org.jetbrains.kotlin.backend.konan.util.suffixIfNot
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.*
|
||||
@@ -34,6 +36,8 @@ import org.jetbrains.kotlin.konan.target.*
|
||||
import org.jetbrains.kotlin.konan.target.TargetManager.*
|
||||
import org.jetbrains.kotlin.konan.target.CompilerOutputKind.*
|
||||
import org.jetbrains.kotlin.konan.util.DependencyProcessor
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.storage.StorageManager
|
||||
|
||||
class KonanConfig(val project: Project, val configuration: CompilerConfiguration) {
|
||||
|
||||
@@ -116,10 +120,25 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
||||
return allMetadata
|
||||
}
|
||||
|
||||
private var forwardDeclarationsModule: ModuleDescriptorImpl? = null
|
||||
|
||||
internal fun getOrCreateForwardDeclarationsModule(
|
||||
builtIns: KotlinBuiltIns, storageManager: StorageManager? = null
|
||||
): ModuleDescriptorImpl {
|
||||
forwardDeclarationsModule?.let { return it }
|
||||
val result = createForwardDeclarationsModule(
|
||||
builtIns,
|
||||
storageManager ?: LockBasedStorageManager()
|
||||
)
|
||||
|
||||
forwardDeclarationsModule = result
|
||||
return result
|
||||
}
|
||||
|
||||
internal val moduleDescriptors: List<ModuleDescriptorImpl> by lazy {
|
||||
for (module in loadedDescriptors) {
|
||||
// Yes, just to all of them.
|
||||
module.setDependencies(loadedDescriptors)
|
||||
module.setDependencies(loadedDescriptors + getOrCreateForwardDeclarationsModule(module.builtIns))
|
||||
}
|
||||
|
||||
loadedDescriptors
|
||||
|
||||
+2
-1
@@ -39,7 +39,8 @@ object TopDownAnalyzerFacadeForKonan {
|
||||
builtIns.builtInsModule = module
|
||||
|
||||
if (!module.isStdlib()) {
|
||||
context.setDependencies(listOf(module) + config.moduleDescriptors)
|
||||
context.setDependencies(listOf(module) + config.moduleDescriptors +
|
||||
config.getOrCreateForwardDeclarationsModule(builtIns, projectContext.storageManager))
|
||||
} else {
|
||||
assert (config.moduleDescriptors.isEmpty())
|
||||
context.setDependencies(module)
|
||||
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.konan.descriptors
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.InteropBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl
|
||||
import org.jetbrains.kotlin.incremental.components.LookupLocation
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl
|
||||
import org.jetbrains.kotlin.storage.StorageManager
|
||||
import org.jetbrains.kotlin.storage.getValue
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
|
||||
|
||||
/**
|
||||
* The package fragment to export forward declarations from interop package namespace, i.e.
|
||||
* redirect "$pkg.$name" to e.g. "cnames.structs.$name".
|
||||
*/
|
||||
class ExportedForwardDeclarationsPackageFragmentDescriptor(
|
||||
module: ModuleDescriptor, fqName: FqName, declarations: List<FqName>
|
||||
) : PackageFragmentDescriptorImpl(module, fqName) {
|
||||
|
||||
private val memberScope = object : MemberScopeImpl() {
|
||||
|
||||
private val nameToFqName = declarations.map { it.shortName() to it }.toMap()
|
||||
|
||||
override fun getContributedClassifier(name: Name, location: LookupLocation): ClassifierDescriptor? {
|
||||
val declFqName = nameToFqName[name] ?: return null
|
||||
|
||||
val packageView = module.getPackage(declFqName.parent())
|
||||
return packageView.memberScope.getContributedClassifier(name, location)
|
||||
}
|
||||
|
||||
override fun printScopeStructure(p: Printer) {
|
||||
p.println(this::class.java.simpleName, " {")
|
||||
p.pushIndent()
|
||||
|
||||
// TODO
|
||||
|
||||
p.popIndent()
|
||||
p.println("}")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun getMemberScope() = memberScope
|
||||
}
|
||||
|
||||
/**
|
||||
* The package fragment that redirects all requests for classifier lookup to its targets.
|
||||
*/
|
||||
class ClassifierAliasingPackageFragmentDescriptor(
|
||||
targets: List<PackageFragmentDescriptor>, module: ModuleDescriptor, fqName: FqName
|
||||
) : PackageFragmentDescriptorImpl(module, fqName) {
|
||||
|
||||
private val memberScope = object : MemberScopeImpl() {
|
||||
|
||||
override fun getContributedClassifier(name: Name, location: LookupLocation) =
|
||||
targets.firstNotNullResult {
|
||||
it.getMemberScope().getContributedClassifier(name, location)
|
||||
}
|
||||
|
||||
override fun printScopeStructure(p: Printer) {
|
||||
p.println(this::class.java.simpleName, " {")
|
||||
p.pushIndent()
|
||||
|
||||
p.println("targets = " + targets)
|
||||
|
||||
p.popIndent()
|
||||
p.println("}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMemberScope(): MemberScope = memberScope
|
||||
}
|
||||
|
||||
/**
|
||||
* Package fragment which creates descriptors for forward declarations on demand.
|
||||
*/
|
||||
private class ForwardDeclarationsPackageFragmentDescriptor(
|
||||
storageManager: StorageManager,
|
||||
module: ModuleDescriptor, fqName: FqName, supertypeName: Name, classKind: ClassKind
|
||||
) : PackageFragmentDescriptorImpl(module, fqName) {
|
||||
|
||||
private val memberScope = object : MemberScopeImpl() {
|
||||
|
||||
private val declarations = storageManager.createMemoizedFunction(this::createDeclaration)
|
||||
|
||||
private val supertype by storageManager.createLazyValue {
|
||||
val descriptor = builtIns.builtInsModule.getPackage(InteropBuiltIns.FqNames.packageName)
|
||||
.memberScope
|
||||
.getContributedClassifier(supertypeName, NoLookupLocation.FROM_BACKEND) as ClassDescriptor
|
||||
|
||||
descriptor.defaultType
|
||||
}
|
||||
|
||||
private fun createDeclaration(name: Name): ClassDescriptor {
|
||||
return ClassDescriptorImpl(
|
||||
this@ForwardDeclarationsPackageFragmentDescriptor,
|
||||
name, Modality.FINAL, classKind,
|
||||
listOf(supertype), SourceElement.NO_SOURCE, false
|
||||
).apply {
|
||||
this.initialize(MemberScope.Empty, emptySet(), null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getContributedClassifier(name: Name, location: LookupLocation) = declarations(name)
|
||||
|
||||
override fun printScopeStructure(p: Printer) {
|
||||
p.println(this::class.java.simpleName, "{}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMemberScope(): MemberScope = memberScope
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates module which "contains" forward declarations.
|
||||
* Note: this module should be unique per compilation and should always be the last dependency of any module.
|
||||
*/
|
||||
fun createForwardDeclarationsModule(builtIns: KotlinBuiltIns, storageManager: StorageManager): ModuleDescriptorImpl {
|
||||
val module = ModuleDescriptorImpl(
|
||||
Name.special("<forward declarations>"),
|
||||
storageManager,
|
||||
builtIns
|
||||
)
|
||||
|
||||
fun createPackage(fqName: FqName, supertypeName: String, classKind: ClassKind = ClassKind.CLASS) =
|
||||
ForwardDeclarationsPackageFragmentDescriptor(
|
||||
storageManager,
|
||||
module,
|
||||
fqName,
|
||||
Name.identifier(supertypeName),
|
||||
classKind
|
||||
)
|
||||
|
||||
val fqNames = InteropBuiltIns.FqNames
|
||||
|
||||
val packageFragmentProvider = PackageFragmentProviderImpl(
|
||||
listOf(
|
||||
createPackage(fqNames.cNamesStructs, "COpaque"),
|
||||
createPackage(fqNames.objCNamesClasses, "ObjCObjectBase"),
|
||||
createPackage(fqNames.objCNamesProtocols, "ObjCObject", ClassKind.INTERFACE)
|
||||
)
|
||||
)
|
||||
|
||||
module.initialize(packageFragmentProvider)
|
||||
module.setDependencies(module)
|
||||
|
||||
return module
|
||||
}
|
||||
+2
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.backend.konan.library
|
||||
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.konan.properties.Properties
|
||||
|
||||
interface KonanLibraryReader {
|
||||
val libraryName: String
|
||||
@@ -25,6 +26,7 @@ interface KonanLibraryReader {
|
||||
val includedPaths: List<String>
|
||||
val linkerOpts: List<String>
|
||||
val escapeAnalysis: ByteArray?
|
||||
val manifestProperties: Properties
|
||||
fun moduleDescriptor(specifics: LanguageVersionSettings): ModuleDescriptor
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.backend.konan.library.impl
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.library.KonanLibraryReader
|
||||
import org.jetbrains.kotlin.backend.konan.createInteropLibrary
|
||||
import org.jetbrains.kotlin.backend.konan.serialization.deserializeModule
|
||||
import org.jetbrains.kotlin.konan.file.File
|
||||
import org.jetbrains.kotlin.konan.properties.*
|
||||
@@ -34,7 +35,7 @@ class LibraryReaderImpl(var libraryFile: File, val currentAbiVersion: Int, val t
|
||||
|
||||
private val reader = MetadataReaderImpl(inPlace)
|
||||
|
||||
val manifestProperties: Properties by lazy {
|
||||
override val manifestProperties: Properties by lazy {
|
||||
inPlace.manifestFile.loadProperties()
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@ class LibraryReaderImpl(var libraryFile: File, val currentAbiVersion: Int, val t
|
||||
reader.loadSerializedPackageFragment(fqName)
|
||||
|
||||
override fun moduleDescriptor(specifics: LanguageVersionSettings)
|
||||
= deserializeModule(specifics, {packageMetadata(it)}, moduleHeaderData)
|
||||
= deserializeModule(specifics, {packageMetadata(it)}, moduleHeaderData, createInteropLibrary(this))
|
||||
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -473,7 +473,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
|
||||
|
||||
if (!useKotlinDispatch) {
|
||||
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! }
|
||||
assert(expression.extensionReceiver == null)
|
||||
assert(expression.dispatchReceiver == null || expression.extensionReceiver == null)
|
||||
|
||||
if (expression.superQualifier?.isObjCMetaClass() == true) {
|
||||
context.reportCompilationError(
|
||||
@@ -493,7 +493,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
|
||||
return builder.genLoweredObjCMethodCall(
|
||||
methodInfo,
|
||||
superQualifier = expression.superQualifierSymbol,
|
||||
receiver = expression.dispatchReceiver!!,
|
||||
receiver = expression.dispatchReceiver ?: expression.extensionReceiver!!,
|
||||
arguments = arguments
|
||||
)
|
||||
}
|
||||
|
||||
+12
-6
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.backend.konan.serialization
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.Context
|
||||
import org.jetbrains.kotlin.backend.konan.InteropLibrary
|
||||
import org.jetbrains.kotlin.backend.konan.KonanBuiltIns
|
||||
import org.jetbrains.kotlin.backend.konan.KonanConfigKeys
|
||||
import org.jetbrains.kotlin.backend.konan.library.LinkData
|
||||
@@ -75,15 +76,19 @@ object NullFlexibleTypeDeserializer : FlexibleTypeDeserializer {
|
||||
}
|
||||
|
||||
fun createKonanPackageFragmentProvider(
|
||||
fragmentNames: List<String>,
|
||||
fragmentNames: List<String>,
|
||||
packageLoader: (String)->KonanLinkData.PackageFragment,
|
||||
storageManager: StorageManager, module: ModuleDescriptor,
|
||||
configuration: DeserializationConfiguration): PackageFragmentProvider {
|
||||
storageManager: StorageManager, module: ModuleDescriptor,
|
||||
configuration: DeserializationConfiguration,
|
||||
interopLibrary: InteropLibrary?): PackageFragmentProvider {
|
||||
|
||||
val packageFragments = fragmentNames.map{
|
||||
KonanPackageFragment(it, packageLoader, storageManager, module)
|
||||
}
|
||||
val provider = PackageFragmentProviderImpl(packageFragments)
|
||||
val syntheticInteropPackageFragments =
|
||||
interopLibrary?.createSyntheticPackages(module, packageFragments) ?: emptyList()
|
||||
|
||||
val provider = PackageFragmentProviderImpl(packageFragments + syntheticInteropPackageFragments)
|
||||
|
||||
val notFoundClasses = NotFoundClasses(storageManager, module)
|
||||
|
||||
@@ -115,7 +120,8 @@ public fun parseModuleHeader(libraryData: ByteArray): Library =
|
||||
KonanSerializerProtocol.extensionRegistry)
|
||||
|
||||
internal fun deserializeModule(languageVersionSettings: LanguageVersionSettings,
|
||||
packageLoader:(String)->ByteArray, library: ByteArray): ModuleDescriptorImpl {
|
||||
packageLoader:(String)->ByteArray, library: ByteArray,
|
||||
interopLibrary: InteropLibrary?): ModuleDescriptorImpl {
|
||||
|
||||
val libraryProto = parseModuleHeader(library)
|
||||
val moduleName = libraryProto.moduleName
|
||||
@@ -131,7 +137,7 @@ internal fun deserializeModule(languageVersionSettings: LanguageVersionSettings,
|
||||
libraryProto.packageFragmentNameList,
|
||||
{it -> parsePackageFragment(packageLoader(it))},
|
||||
storageManager,
|
||||
moduleDescriptor, deserializationConfiguration)
|
||||
moduleDescriptor, deserializationConfiguration, interopLibrary)
|
||||
|
||||
moduleDescriptor.initialize(provider)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import sysstat.*
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val statBuf = nativeHeap.alloc<statStruct>()
|
||||
val statBuf = nativeHeap.alloc<stat>()
|
||||
val res = stat("/", statBuf.ptr)
|
||||
println(res)
|
||||
println(statBuf.st_uid)
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
|
||||
@interface Foo : NSObject
|
||||
@property NSString* name;
|
||||
-(void)hello;
|
||||
-(void)helloWithPrinter:(id <Printer>)printer;
|
||||
@end;
|
||||
|
||||
@interface Foo (FooExtensions)
|
||||
-(void)hello;
|
||||
@end;
|
||||
|
||||
@protocol MutablePair
|
||||
@required
|
||||
@property (readonly) int first;
|
||||
|
||||
@@ -24,11 +24,6 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void)hello {
|
||||
CPrinter* printer = [[CPrinter alloc] init];
|
||||
[self helloWithPrinter:printer];
|
||||
}
|
||||
|
||||
-(void)helloWithPrinter:(id <Printer>)printer {
|
||||
NSString* message = [NSString stringWithFormat:@"Hello, %@!", self.name];
|
||||
[printer print:message.UTF8String];
|
||||
@@ -40,6 +35,14 @@
|
||||
|
||||
@end;
|
||||
|
||||
@implementation Foo (FooExtensions)
|
||||
|
||||
-(void)hello {
|
||||
CPrinter* printer = [[CPrinter alloc] init];
|
||||
[self helloWithPrinter:printer];
|
||||
}
|
||||
@end;
|
||||
|
||||
void replacePairElements(id <MutablePair> pair, int first, int second) {
|
||||
[pair update:0 add:(first - pair.first)];
|
||||
[pair update:1 sub:(pair.second - second)];
|
||||
|
||||
Reference in New Issue
Block a user