Embeddinator-4000/docs/BestPracticesObjC.md

5.5 KiB

id:{932C3F0C-D968-42D1-BB14-D97C73361983} title:Embeddinator-4000 Best Practices for ObjC

//: # (This allows all contributors (including external) to submit, using a PR, updates to the documentation that match the tools changes) //: # (Modifications outside of mono/Embeddinator-4000 will be lost on future updates)

This is a draft and might not be in-sync with the features presently supported by the tool. We hope that this document will evolve separately and eventually match the final tool, i.e. we'll suggest long term best approaches - not immediate workarounds.

A large part of this document also applies to other supported languages. However all provided examples are in C# and ObjC.

Exposing a subset of the managed code

The generated native library/framework contains ObjC code to call each of the managed APIs that is exposed. The more API you surface (make public) then larger the native glue library will become.

It might be a good idea to create a different, smaller assembly, to expose only the required APIs to the native developer. That facade will also allow you more control over the visibility, naming, error checking... of the generated code.

Exposing a chunkier API

There is a price to pay to transition from native to managed (and back). As such, it's better to expose chunky instead of chatty APIs to the native developers, e.g.

Chatty

public class Person {
	public string FirstName { get; set; }
	public string LastName { get; set; }
}
// this requires 3 calls / transitions to initialize the instance
Person *p = [[Person alloc] init];
p.firstName = @"Sebastien";
p.lastName = @"Pouliot";

Chunky

public class Person {
	public Person (string firstName, string lastName) {}
}
// a single call / transition will perform better
Person *p = [[Person alloc] initWithFirstName:@"Sebastien" lastName:@"Pouliot"];

Since the number of transitions is smaller the performance will be better. It also requires less code to be generated, so this will produce a smaller native library as well.

Naming

Naming things is one of two hardest problems in computer science, the others being cache invalidation and off-by-1 errors. Hopefully Embeddinator-4000 can shield you from all but naming.

Types

ObjC does not support namespaces. In general, its types are prefixed with a 2 (for Apple) or 3 (for 3rd parties) character prefix, like UIView for UIKit's View, which denotes the framework.

For .NET types skipping the namespace is not possible as it can introduce duplicated, or confusing, names. This makes existing .NET types very long, e.g.

namespace Xamarin.Xml.Configuration {
	public class Reader {}
}

would be used like:

id reader = [[Xamarin_Xml_Configuration_Reader alloc] init];

However you can re-expose the type as:

public class XAMXmlConfigReader : Xamarin.Xml.Configuration.Reader {}

making it more ObjC friendly to use, e.g.:

id reader = [[XAMXmlConfigReader alloc] init];

Methods

Even good .NET names might not be ideal for an ObjC API.

Naming conventions in ObjC are different than .NET (camel case instead of pascal case, more verbose). Please read the coding guidelines for Cocoa.

From an ObjC developer point of view, a method with a Get prefix implies you do not own the instance, i.e. the get rule.

This naming rule has no match in the .NET GC world; a .NET method with a Create prefix will behave identically in .NET. However, for ObjC developers, it normally means you own the returned instance, i.e. the create rule.

Exceptions

It's quite commont in .NET to use exceptions extensively to report errors. However, they are slow and not quite identical in ObjC. Whenever possible you should hide them from the ObjC developer.

For example, the .NET Try pattern will be much easier to consume from ObjC code.

public int Parse (string number)
{
	return Int32.Parse (number);
}

versus

public bool TryParse (string number, out int value)
{
	return Int32.TryParse (number, out value);
}

Exceptions inside init*

In .NET a constructor must either succeed and return a (hopefully) valid instance or throw an exception.

In contrast, ObjC allows init* to return nil when an instance cannot be created. This is a common, but not general, pattern used in many of Apple's frameworks. In some other cases an assert can happen (and kill the current process).

The generator follow the same return nil pattern for generated init* methods. If a managed exception is thrown, then it will be printed (using NSLog) and nil will be returned to the caller.

Operators

ObjC does not allow operators to be overloaded as C# does, so these are converted to class selectors.

"Friendly" named method are generated in preference to the operator overloads when found, and can produce an easier to consume API.

Classes that override the operators == and\or != should override the standard Equals (Object) method as well.