| name | lang-objc-library-dev |
| description | Objective-C-specific library and framework development patterns. Use when creating reusable frameworks, designing public APIs with protocols and categories, configuring Podspecs/Cartfiles/Package.swift, managing header visibility, implementing XCTest suites, writing HeaderDoc/appledoc documentation, or publishing to CocoaPods/Carthage/SPM. Extends lang-objc-dev with framework tooling and distribution practices. |
Objective-C Library Development
Objective-C-specific patterns for library and framework development. This skill extends lang-objc-dev with framework tooling, API design patterns, and ecosystem practices for creating reusable components.
This Skill Extends
lang-objc-dev- Foundational Objective-C patterns (classes, protocols, categories, memory management)
For general Objective-C syntax, memory management, and Foundation framework usage, see the foundational skill first.
This Skill Adds
- Framework tooling: Xcode framework projects, umbrella headers, module maps
- API design: Public/private header organization, protocol-driven design, category patterns
- Testing: XCTest for libraries, dependency injection, mocking strategies
- Documentation: HeaderDoc, appledoc, inline documentation
- Distribution: CocoaPods, Carthage, Swift Package Manager integration
This Skill Does NOT Cover
- General Objective-C patterns - see
lang-objc-dev - Swift interoperability - see
lang-swift-objc-bridge-dev - iOS/macOS application development - see platform-specific skills
- Advanced design patterns - see
lang-objc-patterns-dev
Quick Reference
| Task | Command/Tool |
|---|---|
| New framework | Xcode: File > New > Project > Framework |
| Build framework | xcodebuild -scheme MyFramework build |
| Run tests | xcodebuild test -scheme MyFramework |
| CocoaPods spec | pod spec create MyLibrary |
| Validate podspec | pod lib lint MyLibrary.podspec |
| Publish to CocoaPods | pod trunk push MyLibrary.podspec |
| Carthage build | carthage build --no-skip-current |
| SPM build | swift build |
| Generate docs | appledoc --project-name MyLib *.h |
Framework Project Structure
Standard Framework Layout
MyLibrary.framework/
├── MyLibrary.xcodeproj
├── MyLibrary/
│ ├── MyLibrary.h # Umbrella header
│ ├── Info.plist
│ ├── Public/ # Public headers
│ │ ├── MLPublicClass.h
│ │ ├── MLProtocol.h
│ │ └── MLConstants.h
│ ├── Private/ # Private headers (not exported)
│ │ └── MLInternal.h
│ └── Implementation/ # .m files
│ ├── MLPublicClass.m
│ └── MLPrivateHelper.m
├── MyLibraryTests/
│ └── MyLibraryTests.m
├── MyLibrary.podspec
├── Cartfile
├── Package.swift
├── README.md
├── CHANGELOG.md
└── LICENSE
Umbrella Header
// MyLibrary.h - Umbrella header that imports all public headers
#import <Foundation/Foundation.h>
//! Project version number for MyLibrary.
FOUNDATION_EXPORT double MyLibraryVersionNumber;
//! Project version string for MyLibrary.
FOUNDATION_EXPORT const unsigned char MyLibraryVersionString[];
// Public headers
#import <MyLibrary/MLPublicClass.h>
#import <MyLibrary/MLProtocol.h>
#import <MyLibrary/MLConstants.h>
#import <MyLibrary/MLCategories.h>
Header Visibility Configuration
In Xcode target settings:
Build Phases > Headers
├── Public # Exported headers (API)
│ ├── MyLibrary.h
│ ├── MLPublicClass.h
│ └── MLProtocol.h
├── Private # Not exported, but visible to framework
│ └── MLInternal.h
└── Project # Completely internal
└── MLPrivateHelper.h
Public API Design
Header Organization Patterns
Pattern 1: Monolithic Umbrella
// Good for small libraries with few classes
#import <MyLibrary/MLClassA.h>
#import <MyLibrary/MLClassB.h>
#import <MyLibrary/MLClassC.h>
Pattern 2: Feature-Based Modules
// Better for larger libraries
#import <MyLibrary/MLCore.h> // Core functionality
#import <MyLibrary/MLNetworking.h> // Network-related
#import <MyLibrary/MLUI.h> // UI components
// Each module header imports its components
// MLCore.h:
#import <MyLibrary/MLCoreClass.h>
#import <MyLibrary/MLCoreProtocol.h>
Pattern 3: Prefix-Based Organization
// Consistent prefix prevents naming conflicts
ML = MyLibrary
MLC = MyLibrary Core
MLN = MyLibrary Networking
MLU = MyLibrary UI
// Examples:
@interface MLCClient : NSObject
@interface MLNRequest : NSObject
@interface MLUButton : UIButton
Protocol-Driven API Design
Define clear protocols for extensibility:
// MLDataProvider.h
@protocol MLDataProvider <NSObject>
@required
/// Fetch data asynchronously
- (void)fetchDataWithCompletion:(void (^)(NSData *_Nullable data, NSError *_Nullable error))completion;
@optional
/// Cancel ongoing fetch operation
- (void)cancelFetch;
/// Configure provider behavior
- (void)configureWithOptions:(NSDictionary<NSString *, id> *)options;
@end
Provide default implementation:
// MLDefaultDataProvider.h
@interface MLDefaultDataProvider : NSObject <MLDataProvider>
/// Designated initializer
- (instancetype)initWithBaseURL:(NSURL *)baseURL NS_DESIGNATED_INITIALIZER;
/// Convenience initializer
+ (instancetype)providerWithBaseURL:(NSURL *)baseURL;
@end
// MLDefaultDataProvider.m
@implementation MLDefaultDataProvider
- (instancetype)initWithBaseURL:(NSURL *)baseURL {
self = [super init];
if (self) {
_baseURL = [baseURL copy];
}
return self;
}
+ (instancetype)providerWithBaseURL:(NSURL *)baseURL {
return [[self alloc] initWithBaseURL:baseURL];
}
- (void)fetchDataWithCompletion:(void (^)(NSData *, NSError *))completion {
// Default implementation
}
@end
Category-Based API Extensions
Provide convenience methods via categories:
// NSString+MLAdditions.h
@interface NSString (MLAdditions)
/// Returns MD5 hash of string
- (NSString *)ml_md5Hash;
/// Returns URL-encoded string
- (NSString *)ml_urlEncode;
/// Safe subscripting that returns nil for out-of-bounds
- (nullable NSString *)ml_substringWithRange:(NSRange)range;
@end
// NSString+MLAdditions.m
@implementation NSString (MLAdditions)
- (NSString *)ml_md5Hash {
// Implementation
const char *cStr = [self UTF8String];
unsigned char digest[CC_MD5_DIGEST_LENGTH];
CC_MD5(cStr, (CC_LONG)strlen(cStr), digest);
NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
[output appendFormat:@"%02x", digest[i]];
}
return output;
}
- (NSString *)ml_urlEncode {
NSCharacterSet *allowed = [NSCharacterSet URLQueryAllowedCharacterSet];
return [self stringByAddingPercentEncodingWithAllowedCharacters:allowed];
}
- (nullable NSString *)ml_substringWithRange:(NSRange)range {
if (range.location + range.length > self.length) {
return nil;
}
return [self substringWithRange:range];
}
@end
Naming convention for categories:
- Prefix all methods with library prefix (e.g.,
ml_) - Prevents collisions with other libraries or Apple's private APIs
- Makes it clear the method is from your library
Constants and Enumerations
Define constants in a dedicated header:
// MLConstants.h
#import <Foundation/Foundation.h>
/// Notification names
FOUNDATION_EXPORT NSNotificationName const MLDidCompleteNotification;
FOUNDATION_EXPORT NSNotificationName const MLDidFailNotification;
/// Error domain
FOUNDATION_EXPORT NSErrorDomain const MLErrorDomain;
/// Error codes
typedef NS_ERROR_ENUM(MLErrorDomain, MLErrorCode) {
MLErrorCodeUnknown = 0,
MLErrorCodeInvalidParameter = 1,
MLErrorCodeNetworkFailure = 2,
MLErrorCodeAuthenticationRequired = 3,
};
/// Configuration keys
FOUNDATION_EXPORT NSString * const MLConfigurationKeyTimeout;
FOUNDATION_EXPORT NSString * const MLConfigurationKeyRetryCount;
/// Options
typedef NS_OPTIONS(NSUInteger, MLOptions) {
MLOptionNone = 0,
MLOptionVerbose = 1 << 0,
MLOptionAsync = 1 << 1,
MLOptionPersistent = 1 << 2,
};
// MLConstants.m
NSNotificationName const MLDidCompleteNotification = @"MLDidCompleteNotification";
NSNotificationName const MLDidFailNotification = @"MLDidFailNotification";
NSErrorDomain const MLErrorDomain = @"com.example.mylibrary.error";
NSString * const MLConfigurationKeyTimeout = @"timeout";
NSString * const MLConfigurationKeyRetryCount = @"retryCount";
Version Information
// MLVersion.h
#import <Foundation/Foundation.h>
/// Current library version
FOUNDATION_EXPORT NSString * const MLVersionString;
/// Version components
FOUNDATION_EXPORT NSInteger const MLVersionMajor;
FOUNDATION_EXPORT NSInteger const MLVersionMinor;
FOUNDATION_EXPORT NSInteger const MLVersionPatch;
// MLVersion.m
NSString * const MLVersionString = @"1.2.3";
NSInteger const MLVersionMajor = 1;
NSInteger const MLVersionMinor = 2;
NSInteger const MLVersionPatch = 3;
XCTest Testing Strategies
Test Target Organization
MyLibraryTests/
├── Unit/
│ ├── MLClassATests.m
│ ├── MLClassBTests.m
│ └── MLUtilsTests.m
├── Integration/
│ ├── MLNetworkingTests.m
│ └── MLDataFlowTests.m
├── Helpers/
│ ├── MLTestHelpers.h
│ ├── MLTestHelpers.m
│ └── MLMockObjects.m
└── Resources/
├── test_data.json
└── fixture.plist
Unit Test Patterns
// MLPublicClassTests.m
#import <XCTest/XCTest.h>
#import <MyLibrary/MyLibrary.h>
@interface MLPublicClassTests : XCTestCase
@property (nonatomic, strong) MLPublicClass *sut; // System Under Test
@end
@implementation MLPublicClassTests
- (void)setUp {
[super setUp];
// Create fresh instance for each test
self.sut = [[MLPublicClass alloc] init];
}
- (void)tearDown {
// Clean up
self.sut = nil;
[super tearDown];
}
#pragma mark - Initialization Tests
- (void)testDesignatedInitializer {
MLPublicClass *instance = [[MLPublicClass alloc] initWithName:@"Test"];
XCTAssertNotNil(instance, @"Instance should not be nil");
XCTAssertEqualObjects(instance.name, @"Test", @"Name should be set");
}
- (void)testConvenienceInitializer {
MLPublicClass *instance = [MLPublicClass instanceWithName:@"Test"];
XCTAssertNotNil(instance);
XCTAssertEqualObjects(instance.name, @"Test");
}
#pragma mark - Behavior Tests
- (void)testProcessDataWithValidInput {
NSData *input = [@"valid data" dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
BOOL result = [self.sut processData:input error:&error];
XCTAssertTrue(result, @"Processing should succeed");
XCTAssertNil(error, @"No error should occur");
}
- (void)testProcessDataWithInvalidInput {
NSError *error = nil;
BOOL result = [self.sut processData:nil error:&error];
XCTAssertFalse(result, @"Processing should fail");
XCTAssertNotNil(error, @"Error should be populated");
XCTAssertEqual(error.code, MLErrorCodeInvalidParameter);
}
#pragma mark - Async Tests
- (void)testAsyncFetchWithExpectation {
XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch completes"];
[self.sut fetchDataWithCompletion:^(NSData *data, NSError *error) {
XCTAssertNotNil(data, @"Data should be fetched");
XCTAssertNil(error, @"No error should occur");
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
if (error) {
XCTFail(@"Timeout waiting for fetch: %@", error);
}
}];
}
#pragma mark - Edge Cases
- (void)testEmptyString {
NSString *result = [self.sut processString:@""];
XCTAssertEqualObjects(result, @"", @"Empty string should be handled");
}
- (void)testNilParameter {
XCTAssertNoThrow([self.sut processString:nil], @"Should handle nil gracefully");
}
- (void)testLargeInput {
NSMutableString *large = [NSMutableString string];
for (int i = 0; i < 100000; i++) {
[large appendString:@"x"];
}
NSString *result = [self.sut processString:large];
XCTAssertNotNil(result, @"Should handle large input");
}
@end
Mock Objects for Testing
// MLMockDataProvider.h
@interface MLMockDataProvider : NSObject <MLDataProvider>
@property (nonatomic, strong) NSData *mockData;
@property (nonatomic, strong) NSError *mockError;
@property (nonatomic, assign) BOOL shouldFail;
@property (nonatomic, assign) NSTimeInterval delay;
@end
// MLMockDataProvider.m
@implementation MLMockDataProvider
- (void)fetchDataWithCompletion:(void (^)(NSData *, NSError *))completion {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.delay * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
if (self.shouldFail) {
completion(nil, self.mockError);
} else {
completion(self.mockData, nil);
}
});
}
@end
// Usage in tests:
- (void)testWithMockProvider {
MLMockDataProvider *mock = [[MLMockDataProvider alloc] init];
mock.mockData = [@"test" dataUsingEncoding:NSUTF8StringEncoding];
mock.shouldFail = NO;
MLClient *client = [[MLClient alloc] initWithDataProvider:mock];
// Test client with predictable mock behavior
}
Test Helpers
// MLTestHelpers.h
@interface MLTestHelpers : NSObject
/// Load JSON from test bundle
+ (NSDictionary *)loadJSONFixture:(NSString *)filename;
/// Create temporary directory for test files
+ (NSURL *)createTemporaryDirectory;
/// Clean up test resources
+ (void)cleanupTemporaryDirectory:(NSURL *)directory;
@end
// MLTestHelpers.m
@implementation MLTestHelpers
+ (NSDictionary *)loadJSONFixture:(NSString *)filename {
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSURL *url = [bundle URLForResource:filename withExtension:@"json"];
NSData *data = [NSData dataWithContentsOfURL:url];
return [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
}
+ (NSURL *)createTemporaryDirectory {
NSString *tempDir = NSTemporaryDirectory();
NSString *guid = [[NSUUID UUID] UUIDString];
NSString *path = [tempDir stringByAppendingPathComponent:guid];
[[NSFileManager defaultManager] createDirectoryAtPath:path
withIntermediateDirectories:YES
attributes:nil
error:nil];
return [NSURL fileURLWithPath:path];
}
+ (void)cleanupTemporaryDirectory:(NSURL *)directory {
[[NSFileManager defaultManager] removeItemAtURL:directory error:nil];
}
@end
HeaderDoc and Documentation
HeaderDoc Syntax
// MLPublicClass.h
/*!
@class MLPublicClass
@abstract A brief description of the class
@discussion
A more detailed description of what this class does,
its purpose, and how it should be used.
Example usage:
@code
MLPublicClass *instance = [[MLPublicClass alloc] initWithName:@"Example"];
[instance processData:data error:&error];
@endcode
@note This class is thread-safe.
@warning Do not use this class in a tight loop.
*/
@interface MLPublicClass : NSObject
/*!
@property name
@abstract The name associated with this instance
@discussion
The name is used to identify this instance in logs and error messages.
Must not be nil or empty.
*/
@property (nonatomic, copy, readonly) NSString *name;
/*!
@method initWithName:
@abstract Designated initializer
@param name The name for this instance. Must not be nil.
@return An initialized instance, or nil if name is invalid
@discussion
This is the designated initializer for this class. All other
initializers should call this method.
@throws NSInvalidArgumentException if name is nil
*/
- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER;
/*!
@method instanceWithName:
@abstract Convenience class method to create instances
@param name The name for the instance
@return A new autoreleased instance
*/
+ (instancetype)instanceWithName:(NSString *)name;
/*!
@method processData:error:
@abstract Process input data
@param data The data to process. Must not be nil.
@param error On input, a pointer to an error object. If an error occurs,
this pointer is set to an NSError object describing the error.
Pass NULL if you don't care about error information.
@return YES if processing succeeded, NO otherwise
@discussion
This method processes the input data according to the configured rules.
If processing fails, the error parameter will be populated with details.
Error codes:
- MLErrorCodeInvalidParameter: data parameter was nil
- MLErrorCodeProcessingFailed: processing encountered an error
@see fetchDataWithCompletion:
*/
- (BOOL)processData:(NSData *)data error:(NSError **)error;
/*!
@method fetchDataWithCompletion:
@abstract Asynchronously fetch data
@param completion Completion block called when fetch completes or fails.
Called on the main queue.
@discussion
This method fetches data asynchronously. The completion block is always
called, either with data on success or an error on failure.
@code
[instance fetchDataWithCompletion:^(NSData *data, NSError *error) {
if (error) {
NSLog(@"Failed: %@", error);
} else {
// Use data
}
}];
@endcode
*/
- (void)fetchDataWithCompletion:(void (^)(NSData *_Nullable data, NSError *_Nullable error))completion;
@end
Nullability Annotations
NS_ASSUME_NONNULL_BEGIN
@interface MLClient : NSObject
/// Returns a value, never nil
- (NSString *)getName;
/// May return nil
- (nullable NSString *)getOptionalValue;
/// Takes non-null parameter
- (void)setName:(NSString *)name;
/// Takes nullable parameter
- (void)setOptionalValue:(nullable NSString *)value;
/// Block with nullable parameters and return
- (void)fetchWithCompletion:(void (^)(NSData *_Nullable data, NSError *_Nullable error))completion;
@end
NS_ASSUME_NONNULL_END
Appledoc Configuration
Create a .appledoc configuration file:
--project-name "MyLibrary"
--project-company "Your Company"
--company-id "com.yourcompany"
--output "./Documentation"
--logformat xcode
--keep-intermediate-files
--no-repeat-first-par
--no-warn-invalid-crossref
--ignore "*.m"
--ignore "Private"
--index-desc "./README.md"
Generate documentation:
appledoc .
CocoaPods Distribution
Podspec Structure
# MyLibrary.podspec
Pod::Spec.new do |s|
# Metadata
s.name = 'MyLibrary'
s.version = '1.2.3'
s.summary = 'A brief description of MyLibrary'
s.description = <<-DESC
A longer description of MyLibrary that explains what it does,
why it exists, and what problems it solves. This should be
more detailed than the summary.
DESC
# Homepage and license
s.homepage = 'https://github.com/yourusername/mylibrary'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Your Name' => 'you@example.com' }
s.source = { :git => 'https://github.com/yourusername/mylibrary.git',
:tag => s.version.to_s }
# Platform requirements
s.ios.deployment_target = '12.0'
s.osx.deployment_target = '10.13'
s.watchos.deployment_target = '5.0'
s.tvos.deployment_target = '12.0'
# Source files
s.source_files = 'MyLibrary/**/*.{h,m}'
s.public_header_files = 'MyLibrary/Public/**/*.h'
s.private_header_files = 'MyLibrary/Private/**/*.h'
# Resources
s.resources = 'MyLibrary/Resources/**/*'
# Dependencies
s.dependency 'AFNetworking', '~> 4.0'
s.ios.dependency 'SDWebImage', '~> 5.0'
# Frameworks
s.frameworks = 'Foundation', 'UIKit'
# Build settings
s.requires_arc = true
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES'
}
# Subspecs for modular features
s.subspec 'Core' do |core|
core.source_files = 'MyLibrary/Core/**/*.{h,m}'
core.public_header_files = 'MyLibrary/Core/Public/**/*.h'
end
s.subspec 'Networking' do |net|
net.source_files = 'MyLibrary/Networking/**/*.{h,m}'
net.dependency 'MyLibrary/Core'
net.dependency 'AFNetworking', '~> 4.0'
end
s.default_subspecs = 'Core'
end
CocoaPods Workflow
# Create podspec
pod spec create MyLibrary
# Validate locally
pod lib lint MyLibrary.podspec
# Validate with remote sources
pod spec lint MyLibrary.podspec
# Register with CocoaPods trunk (first time only)
pod trunk register you@example.com 'Your Name'
# Push to CocoaPods
pod trunk push MyLibrary.podspec
# Add collaborators
pod trunk add-owner MyLibrary other@example.com
Podfile for Development
# Podfile for development/testing
platform :ios, '12.0'
use_frameworks!
target 'MyLibrary' do
# Your library dependencies
pod 'AFNetworking', '~> 4.0'
end
target 'MyLibraryTests' do
# Test dependencies
pod 'OCMock', '~> 3.9'
end
# Use local development pod
# pod 'MyLibrary', :path => './'
Carthage Distribution
Cartfile Structure
# Cartfile - Dependencies for your library
github "AFNetworking/AFNetworking" ~> 4.0
Carthage Compatibility
Ensure your framework is Carthage-compatible:
- Use shared schemes - Make sure your framework scheme is shared
- Build universal frameworks - Support both iOS devices and simulator
# Build for Carthage
carthage build --no-skip-current
# Create XCFramework (Xcode 11+)
carthage build --use-xcframeworks
Build Script for Universal Framework
#!/bin/bash
# build-universal.sh
FRAMEWORK_NAME="MyLibrary"
SCHEME="MyLibrary"
# Build for device
xcodebuild archive \
-scheme "${SCHEME}" \
-archivePath "build/ios.xcarchive" \
-sdk iphoneos \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
# Build for simulator
xcodebuild archive \
-scheme "${SCHEME}" \
-archivePath "build/ios-simulator.xcarchive" \
-sdk iphonesimulator \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
# Create XCFramework
xcodebuild -create-xcframework \
-framework "build/ios.xcarchive/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \
-framework "build/ios-simulator.xcarchive/Products/Library/Frameworks/${FRAMEWORK_NAME}.framework" \
-output "build/${FRAMEWORK_NAME}.xcframework"
Swift Package Manager
Package.swift
// swift-tools-version:5.5
import PackageDescription
let package = Package(
name: "MyLibrary",
platforms: [
.iOS(.v12),
.macOS(.v10_13),
.watchOS(.v5),
.tvOS(.v12)
],
products: [
.library(
name: "MyLibrary",
targets: ["MyLibrary"]),
],
dependencies: [
// External dependencies
.package(url: "https://github.com/AFNetworking/AFNetworking.git", from: "4.0.0"),
],
targets: [
.target(
name: "MyLibrary",
dependencies: ["AFNetworking"],
path: "MyLibrary",
publicHeadersPath: "Public",
cSettings: [
.headerSearchPath("Private"),
]),
.testTarget(
name: "MyLibraryTests",
dependencies: ["MyLibrary"],
path: "MyLibraryTests"),
]
)
SPM Directory Structure
MyLibrary/
├── Package.swift
├── Sources/
│ └── MyLibrary/
│ ├── include/ # Public headers
│ │ ├── MyLibrary.h
│ │ ├── MLPublicClass.h
│ │ └── MLProtocol.h
│ └── Implementation/ # .m files
│ └── MLPublicClass.m
├── Tests/
│ └── MyLibraryTests/
│ └── MyLibraryTests.m
└── README.md
Module Maps
Custom Module Map
// module.modulemap
framework module MyLibrary {
umbrella header "MyLibrary.h"
export *
module * { export * }
explicit module Private {
header "MLInternal.h"
export *
}
}
Module Map in Xcode
Configure in Build Settings:
DEFINES_MODULE = YES
MODULEMAP_FILE = $(SRCROOT)/MyLibrary/module.modulemap
Versioning and Release Strategy
Semantic Versioning
Follow semver.org:
- MAJOR: Incompatible API changes
- MINOR: Backwards-compatible functionality additions
- PATCH: Backwards-compatible bug fixes
Deprecation Strategy
// Deprecate old API
- (void)oldMethod:(id)parameter
DEPRECATED_MSG_ATTRIBUTE("Use newMethod:withOptions: instead");
// Introduce replacement
- (void)newMethod:(id)parameter
withOptions:(NSDictionary *)options;
// Provide compatibility shim
- (void)oldMethod:(id)parameter {
NSLog(@"Warning: oldMethod: is deprecated. Use newMethod:withOptions:");
[self newMethod:parameter withOptions:@{}];
}
Version Macro
// MLVersion.h
#define ML_VERSION_MAJOR 1
#define ML_VERSION_MINOR 2
#define ML_VERSION_PATCH 3
#define ML_VERSION_CHECK(major, minor, patch) \
(ML_VERSION_MAJOR > (major) || \
(ML_VERSION_MAJOR == (major) && ML_VERSION_MINOR > (minor)) || \
(ML_VERSION_MAJOR == (major) && ML_VERSION_MINOR == (minor) && ML_VERSION_PATCH >= (patch)))
// Usage:
#if ML_VERSION_CHECK(1, 2, 0)
// Code for version 1.2.0 and later
#endif
CHANGELOG.md
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- New feature in development
## [1.2.3] - 2024-01-15
### Fixed
- Fixed crash when processing nil data
- Corrected memory leak in network layer
## [1.2.0] - 2024-01-01
### Added
- New `MLClient` class for network operations
- Support for custom data providers via `MLDataProvider` protocol
### Changed
- Improved performance of data processing by 50%
- Updated minimum deployment target to iOS 12.0
### Deprecated
- `oldMethod:` in favor of `newMethod:withOptions:`
## [1.1.0] - 2023-12-01
### Added
- Initial public release
Troubleshooting
Framework Not Found at Runtime
Symptoms: dyld: Library not loaded: @rpath/MyLibrary.framework/MyLibrary
Solution:
1. Check Build Settings > Runpath Search Paths
- Should include @executable_path/Frameworks
2. Verify framework is embedded:
- Build Phases > Embed Frameworks
- Framework should be listed
3. For CocoaPods, ensure:
- use_frameworks! is in Podfile
- Run pod install after changes
Duplicate Symbol Errors
Symptoms: duplicate symbol _OBJC_CLASS_$_ClassName
Solution:
// Ensure category methods use unique prefix
// BAD:
@implementation NSString (Additions)
- (NSString *)reverse { ... }
@end
// GOOD:
@implementation NSString (MLAdditions)
- (NSString *)ml_reverse { ... }
@end
Missing Header in Umbrella
Symptoms: Header not visible to framework users
Solution:
1. Check header visibility in Xcode:
- Target > Build Phases > Headers
- Move header from Project/Private to Public
2. Add to umbrella header:
- #import <MyLibrary/MLNewClass.h>
3. Clean and rebuild:
- Product > Clean Build Folder (Cmd+Shift+K)
Swift Bridging Issues
Symptoms: Objective-C types not visible in Swift
Solution:
// Use nullability annotations
NS_ASSUME_NONNULL_BEGIN
@interface MLClass : NSObject
- (NSString *)getName; // Nonnull by default
- (nullable NSString *)getOptionalName;
@end
NS_ASSUME_NONNULL_END
// Use generics for collections
@property (nonatomic, copy) NSArray<NSString *> *items;
@property (nonatomic, copy) NSDictionary<NSString *, NSNumber *> *counts;
Podspec Validation Failures
Symptoms: pod lib lint fails
Solution:
# Get verbose output
pod lib lint --verbose
# Common issues:
# 1. Missing source files
# - Check s.source_files pattern matches actual files
# 2. Dependency conflicts
# - Ensure dependency versions are compatible
# - Use ~> for flexible version matching
# 3. Build settings issues
# - Add necessary frameworks to s.frameworks
# - Set s.requires_arc appropriately
# 4. Missing license
# - Ensure LICENSE file exists
# - Match s.license specification
Best Practices Summary
- Prefix all public symbols with 2-3 letter prefix (classes, protocols, constants, categories)
- Use nullability annotations for Swift interoperability
- Document with HeaderDoc for generated documentation
- Provide protocol-based APIs for extensibility
- Include comprehensive tests with XCTest
- Support multiple distribution methods (CocoaPods, Carthage, SPM)
- Follow semantic versioning strictly
- Maintain backwards compatibility when possible
- Deprecate before removing APIs
- Keep umbrella header clean and organized
See Also
lang-objc-dev- Foundational Objective-C patternslang-swift-objc-bridge-dev- Swift interoperabilitylang-objc-patterns-dev- Advanced design patterns- CocoaPods Guides
- Carthage Documentation
- Swift Package Manager Documentation
- HeaderDoc User Guide