Apple Cocoa Bindings Programming Topics Manuel
Apple Cocoa Bindings Programming Topics Manuel
Apple sur Fnac.com
- Pour voir la liste complète des manuels APPLE, cliquez ici
TELECHARGER LE PDF sur :
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaBindings/CocoaBindings.pdf
Commander un produit Apple sur Fnac.com
Voir également d'autres Guides et documentation APPLE :
Apple-URL-Loading-System-Programming-Guide-manuel
Apple-URL-Loading-System-Programming-Guide-manuel
Apple-Document-Based-App-Programming-Guide-for-iOS-Guide-manuel
Apple-Core-Text-Programming-Guide-manuel
Apple-OpenCL-Programming-Guide-for-Mac-manuel
Apple-Event-Driven-XML-Guide-manuel
Apple-Date-and-Time-Guide-manuel
Apple-Windows-Guide-manuel
Apple-AV-Foundation-Programming-Guide-manuel
Apple-Preferences-and-Settings-Programming-Guide-manuel
Apple-OpenGL-Programming-Guide-for-Mac-Guide-manuel
Apple-View-Programming-Guide-for-iOS-Guide-manuel
Apple-Stream-Programming-Guide-manuel
Apple-multimediaprogrammingguide.pdf-Guide-manuel
Apple-External-Accessory-Programming-Topics-Guide-manuel
Apple-Blocks-Programming-Topics-Guide-manuel
Apple-CocoaEncyclopedia.pdf-Guide-manuel
Apple-Mac-App-Programming-Guide-manuel
Apple-Local-and-Push-Notification-Programming-Guide-manuel
Apple-Objective-C-Runtime-Programming-Guide-manuel
Apple-Universal-Binary-Programming-Guidelines-Second-Edition-Guide-manuel
Apple-TableView_iPhone.pdf-Guide-manuel
Apple-Object-Oriented-Programming-with-Objective-C-Guide-manuel
Apple-airportextremebasestationsetupguide.pdf-Guide-manuel
Apple-iPhone_4_Deux_doigts_d_astuces.pdf-Guide-manuel
Apple-iPod_nano_4th_gen_UserGuide.pdf-Guide-manuel
Apple-livetype_2_user_manual.pdf-Guide-manuel
Apple-TV_2nd_gen_Setup_Guide.pdf-manuel
Apple-Archives-and-Serializations-Programming-manuel
Apple-SafariWebContent.pdf-Guide-manuel
Apple-iTunes_ExtrasandiTunes_LPTestGuide1.1.pdf-manuel
Apple-Text-System-User-Interface-Layer-Programming-manuel
Apple-CocoaTextArchitecture.pdf-manuel
Apple-Key-Value-Observing-Programming-Guide-manuel
Apple-Location-Awareness-Programming-Guide-manuel
Apple-SharkUserGuide.pdf-manuel
Apple-drawingprintingios.pdf-manuel
Apple-QuickTime7_User_Guide.pdf-manuel
Apple-Event-Handling-Guide-for-iOS-manuel
Apple-ipod_nano_3rd_gen_features_guide.pdf-manuel
Apple-iTunes_VideoandAudio_Asset_Guide5.0.pdf-manuel
Apple-ARD3_AdminGuide.pdf-manuel
Apple-SafariWebContent.pdf-Guide-manue
Apple-iphone_3gs_finger_tips.pdf-manuel
Apple-InstrumentsUserGuide.pdf-manuel
Apple-Logic-Pro-9-TDM-Guide-manuel
Apple-macbook_air_users_guide.pdf-manuel
Apple-macbook_air-13-inch_mid-2012-qs_ta.pdf-manuel
Apple-AppStoreMarketingGuidelines-JP.pdf-Japon-manuel
Apple-macbook_pro_retina_qs_ta.pdf-manuel
Apple-ipad_user_guide_tu.pdf-manuel
Apple-ipad_user_guide_th.pdf-manuel
Apple-iphone_user_guide_gr.pdf-manuel
Apple-Nike_Plus_iPod_Sensor_UG_2A.pdf-manuel
Apple-ipad_manual_del_usuario.pdf-manuel
Apple-ipad_uzivatelska_prirucka.pdf-manuel
Apple-ipad_wifi_informations_importantes.pdf-manuel
Apple-Xsan_2_Admin_Guide_v2.3.pdf-manuel
Apple-macbook_pro-13-inch-late-2012-quick_start.pdf-manuel
Apple-CocoaDrawingGuide.pdf-manuel
Apple-Cryptographic-Services-Guide-manuel
Apple-Resource-Programming-Guide-manuel
AppleSafariVisualEffectsProgGuide.pdf-manuel
/Apple-WorkingWithUSB.pdf-manuel
Apple-macbook_pro-retina-mid-2012-important_product_info_f.pdf-manuel
Apple-iOS_Security_May12.pdf-manue
Apple-Mac-Pro-2008-Performance-and-Productivity-for-Creative-Pros
Apple-iPod_shuffle_4thgen_Manuale_utente.pdf-Italie-Manuel
Apple-KernelProgramming.pdf-manuel
Apple-Core-Data-Model-Versioning-and-Data-Migration-Programming-Guide-manuel
Apple-RED_Workflows_with_Final_Cut_Pro_X.pdf-manuel
Apple-Transitioning-to-ARC-Release-Notes-manuel
Apple-iTunes-Connect-Sales-and-Trends-Guide-manuel
Apple-App-Sandbox-Design-Guide-manuel
Apple-String-Programming-Guide-manuel
Apple-Secure-Coding-Guide-manuel
Apple_AirPort_Networks_Early2009.pdf-manuel
Apple-TimeCapsule_SetupGuide_TA.pdf-manuel
Apple-time_capsule_4th_gen_setup.pdf-manuel
Apple-TimeCapsule_SetupGuide.pdf-manuel
Apple-TimeCapsule_SetupGuide_CH.pdf-Chinois-manuel
Apple-CodeSigningGuide.pdf-manuel
Apple-ViewControllerPGforiOS.pdf-manuel
Apple-KeyValueObserving.pdf-manuel
Apple-mac_mini-late-2012-quick_start.pdf-manuel
Apple-OS-X-Mountain-Lion-Core-Technologies-Overview-June-2012-manuel
Apple-OS-X-Server-Product-Overview-June-2012-manuel
Apple-Apple_Server_Diagnostics_UG_109.pdf-manuel
Apple-PackageMaker_UserGuide.pdf-manuel
Apple-InstrumentsUserGuide.pdf-manuel
Apple-Logic-Pro-9-TDM-Guide-manuel
Apple-macbook_air_users_guide.pdf-manuel
Apple-macbook_air-13-inch_mid-2012-qs_ta.pdf-manuel
Apple-AppStoreMarketingGuidelines-JP.pdf-Japon-manuel
Apple-macbook_pro_retina_qs_ta.pdf-manuel
Apple-ipad_user_guide_tu.pdf-manuel
Apple-ipad_user_guide_th.pdf-manuel
Apple-iphone_user_guide_gr.pdf-manuel
Apple-Nike_Plus_iPod_Sensor_UG_2A.pdf-manuel
Apple-ipad_manual_del_usuario.pdf-manuel
Apple-ipad_uzivatelska_prirucka.pdf-manuel
Apple-ipad_wifi_informations_importantes.pdf-manuel
Apple-Xsan_2_Admin_Guide_v2.3.pdf-manuel
Apple-macbook_pro-13-inch-late-2012-quick_start.pdf-manuel
Apple-CocoaDrawingGuide.pdf-manuel
Apple-Cryptographic-Services-Guide-manuel
Apple-Resource-Programming-Guide-manuel
AppleSafariVisualEffectsProgGuide.pdf-manuel
/Apple-WorkingWithUSB.pdf-manuel
Apple-macbook_pro-retina-mid-2012-important_product_info_f.pdf-manuel
Apple-iOS_Security_May12.pdf-manue
Apple-Mac-Pro-2008-Performance-and-Productivity-for-Creative-Pros
Apple-iPod_shuffle_4thgen_Manuale_utente.pdf-Italie-Manuel
Apple-KernelProgramming.pdf-manuel
Apple-Core-Data-Model-Versioning-and-Data-Migration-Programming-Guide-manuel
Apple-RED_Workflows_with_Final_Cut_Pro_X.pdf-manuel
Apple-Transitioning-to-ARC-Release-Notes-manuel
Apple-iTunes-Connect-Sales-and-Trends-Guide-manuel
Apple-App-Sandbox-Design-Guide-manuel
Apple-String-Programming-Guide-manuel
Apple-Secure-Coding-Guide-manuel
Apple_AirPort_Networks_Early2009.pdf-manuel
Apple-TimeCapsule_SetupGuide_TA.pdf-manuel
Apple-time_capsule_4th_gen_setup.pdf-manuel
Apple-TimeCapsule_SetupGuide.pdf-manuel
Apple-TimeCapsule_SetupGuide_CH.pdf-Chinois-manuel
Apple-CodeSigningGuide.pdf-manuel
Apple-ViewControllerPGforiOS.pdf-manuel
Apple-KeyValueObserving.pdf-manuel
Apple-mac_mini-late-2012-quick_start.pdf-manuel
Apple-OS-X-Mountain-Lion-Core-Technologies-Overview-June-2012-manuel
Apple-OS-X-Server-Product-Overview-June-2012-manuel
Apple-Apple_Server_Diagnostics_UG_109.pdf-manuel
Apple-PackageMaker_UserGuide.pdf-manuel
Apple-Instrumentos_y_efectos_de_Logic_Studio.pdf-Manuel
Apple-ipod_nano_kayttoopas.pdf-Finlande-Manuel
Apple_ProRes_White_Paper_October_2012.pdf-Manuel
Apple-wp_osx_configuration_profiles.pdf-Manuel
Apple-UsingiTunesProducerFreeBooks.pdf-Manuel
Apple-ipad_manual_do_usuario.pdf-Portugais-Manuel
Apple-Instruments_et_effets_Logic_Studio.pdf-Manuel
Apple-ipod_touch_gebruikershandleiding.pdf-Neerlandais-Manuel
AppleiPod_shuffle_4thgen_Manual_del_usuario.pdf-Espagnol-Manuel
Apple-Premiers-contacts-avec-votre-PowerBook-G4-Manuel
Apple_Composite_AV_Cable.pdf-Manuel
Apple-iPod_shuffle_3rdGen_UG_DK.pdf-Danemark-Manuel
Apple-iPod_classic_160GB_Benutzerhandbuch.pdf-Allemand-Manuel
Apple-VoiceOver_GettingStarted-Manuel
Apple-iPod_touch_2.2_Benutzerhandbuch.pdf-Allemand-Manuel
Apple-Apple_TV_Opstillingsvejledning.pdf-Allemand-Manuel
Apple-iPod_shuffle_4thgen_Manuale_utente.pdf-Italie-Manuel
Apple-iphone_prirucka_uzivatela.pdf-Manuel
Apple-Aan-de-slag-Neerlandais-Manuel
Apple-airmac_express-80211n-2nd-gen_setup_guide.pdf-Thailande-Manuel
Apple-ipod_nano_benutzerhandbuch.pdf-Allemand-Manuel
Apple-aperture3.4_101.pdf-Manuel
Apple-Pages09_Anvandarhandbok.pdf-Manuel
Apple-nike_plus_ipod_sensor_ug_la.pdf-Mexique-Manuel
Apple-ResEdit-Reference-For-ResEdit02.1-Manuel
Apple-ipad_guide_de_l_utilisateur.pdf-Manuel
Apple-Compressor-4-Benutzerhandbuch-Allemand-Manuel
Apple-AirPort_Networks_Early2009_DK.pdf-Danemark-Manuel
Apple-MacBook_Pro_Mid2007_2.4_2.2GHz_F.pdf-Manuel
Apple-MacBook_13inch_Mid2010_UG_F.pdf-Manuel
Apple-Xserve-RAID-Presentation-technologique-Janvier-2004-Manuel
Apple-MacBook_Pro_15inch_Mid2010_F.pdf-Manuel
Apple-AirPort_Express-opstillingsvejledning.pdf-Danemark-Manuel
Apple-DEiPod_photo_Benutzerhandbuch_DE0190269.pdf-Allemand-Manuel
Apple-Final-Cut-Pro-X-Logic-Effects-Reference-Manuel
Apple-iPod_touch_2.1_Brugerhandbog.pdf-Danemark-Manuel
Apple-Remote-Desktop-Administratorhandbuch-Version-3.1-Allemand-Manuel
Apple-Qmaster-4-User-Manual-Manuel
Apple-Server_Administration_v10.5.pdf-Manuel
Apple-ipod_classic_features_guide.pdf-Manuel
Apple-Lecteur-Optique-Manuel
Apple-Carte-AirPort-Manuel
Apple-iPhone_Finger_Tips_Guide.pdf-Anglais-Manuel
Apple-Couvercle-Manuel
Apple-battery.cube.pdf-Manuel
Apple-Boitier-de-l-ordinateur-Manuel
Apple-Pile-Interne-Manuel
Apple-atacable.pdf-Manuel
Apple-videocard.pdf-Manuel
Apple-Guide_de_configuration_de_l_Airport_Express_5.1.pdf-Manuel
Apple-iMac_Mid2010_UG_F.pdf-Manuel
Apple-MacBook_13inch_Mid2009_F.pdf-Manuel
Apple-MacBook_Mid2007_UserGuide.F.pdf-Manuel
Apple-Designing_AirPort_Networks_10.5-Windows_F.pdf-Manuel
Apple-Administration_de_QuickTime_Streaming_et_Broadcasting_10.5.pdf-Manuel
Apple-Opstillingsvejledning_til_TimeCapsule.pdf-Danemark-Manuel
Apple-iPod_nano_5th_gen_Benutzerhandbuch.pdf-Manuel
Apple-iOS_Business.pdf-Manuel
Apple-AirPort_Extreme_Installationshandbuch.pdf-Manuel
Apple-Final_Cut_Express_4_Installation_de_votre_logiciel.pdf-Manuel
Apple-MacBook_Pro_15inch_2.53GHz_Mid2009.pdf-Manuel
Apple-Network_Services.pdf-Manuel
Apple-Aperture_Performing_Adjustments_f.pdf-Manuel
Apple-Supplement_au_guide_Premiers_contacts.pdf-Manuel
Apple-Administration_des_images_systeme_et_de_la_mise_a_jour_de_logiciels_10.5.pdf-Manuel
Apple-Mac_OSX_Server_v10.6_Premiers_contacts.pdf-Francais-Manuel
Apple-Designing_AirPort_Networks_10.5-Windows_F.pdf-Manuel
Apple-Mise_a_niveau_et_migration_v10.5.pdf-Manue
Apple-MacBookPro_Late_2007_2.4_2.2GHz_F.pdf-Manuel
Apple-Mac_mini_Late2009_SL_Server_F.pdf-Manuel
Apple-Mac_OS_X_Server_10.5_Premiers_contacts.pdf-Manuel
Apple-iPod_touch_2.0_Guide_de_l_utilisateur_CA.pdf-Manuel
Apple-MacBook_Pro_17inch_Mid2010_F.pdf-Manuel
Apple-Comment_demarrer_Leopard.pdf-Manuel
Apple-iPod_2ndGen_USB_Power_Adapter-FR.pdf-Manuel
Apple-Feuille_de_operations_10.4.pdf-Manuel
Apple-Time_Capsule_Installationshandbuch.pdf-Allemand-Manuel
Apple-F034-2262AXerve-grappe.pdf-Manuel
Apple-Mac_Pro_Early2009_4707_UG_F
Apple-imacg5_17inch_Power_Supply
Apple-Logic_Studio_Installieren_Ihrer_Software_Retail
Apple-IntroductionXserve1.0.1
Apple-Aperture_Getting_Started_d.pdf-Allemand
Apple-getting_started_with_passbook
Apple-iPod_mini_2nd_Gen_UserGuide.pdf-Anglais
Apple-Deploiement-d-iPhone-et-d-iPad-Reseaux-prives-virtuels
Apple-F034-2262AXerve-grappe
Apple-Mac_OS_X_Server_Glossaire_10.5
Apple-FRLogic_Pro_7_Guide_TDM
Apple-iphone_bluetooth_headset_userguide
Apple-Administration_des_services_reseau_10.5
Apple-imacg5_17inch_harddrive
Apple-iPod_nano_4th_gen_Manuale_utente
Apple-iBook-G4-Getting-Started
Apple-XsanGettingStarted
Apple-Mac_mini_UG-Early2006
Apple-Guide_des_fonctionnalites_de_l_iPod_classic
Apple-Guide_de_configuration_d_Xsan_2
Apple-MacBook_Late2006_UsersGuide
Apple-sur-Fnac.com
Apple-Mac_mini_Mid2010_User_Guide_F.pdf-Francais
Apple-PowerBookG3UserManual.PDF.Anglais
Apple-Installation_de_votre_logiciel_Logic_Studio_Retail
Apple-Pages-Guide-de-l-utilisateur
Apple-MacBook_Pro_13inch_Mid2009.pdf.Anglais
Apple-MacBook_Pro_15inch_Mid2009
Apple-Installation_de_votre_logiciel_Logic_Studio_Upgrade
Apple-FRLogic_Pro_7_Guide_TDM
Apple-airportextreme_802.11n_userguide
Apple-iPod_shuffle_3rdGen_UG
Apple-iPod_classic_160GB_User_Guide
Apple-iPod_nano_5th_gen_UserGuide
Apple-ipod_touch_features_guide
Apple-Wireless_Mighty_Mouse_UG
Apple-Advanced-Memory-Management-Programming-Guide
Apple-iOS-App-Programming-Guide
Apple-Concurrency-Programming-Guide
Apple-MainStage-2-User-Manual-Anglais
Apple-iMacG3_2002MultilingualUserGuide
Apple-iBookG3_DualUSBUserGuideMultilingual.PDF.Anglais
Apple-imacG5_20inch_AirPort
Apple-Guide_de_l_utilisateur_de_Mac_Pro_Early_2008
Apple-Installation_de_votre_logiciel_Logic_Express_8
Apple-iMac_Guide_de_l_utilisateur_Mid2007
Apple-imacg5_20inch_OpticalDrive
Apple-FCP6_Formats_de_diffusion_et_formats_HD
Apple-prise_en_charge_des_surfaces_de_controle_logic_pro_8
Apple-Aperture_Quick_Reference_f
Apple-Shake_4_User_Manual
Apple-aluminumAppleKeyboard_wireless2007_UserGuide
Apple-ipod_shuffle_features_guide
Apple-Color-User-Manual
Apple-XsanGettingStarted
Apple-Migration_10.4_2e_Ed
Apple-MacBook_Air_SuperDrive
Apple-MacBook_Late2007-f
ApplePowerMacG5_(Early_2005)_UserGuide
Apple-iSightUserGuide
Apple-MacBook_Pro_Early_2008_Guide_de_l_utilisateur
Apple-Nouvelles-fonctionnalites-aperture-1.5
Apple-premiers_contacts_2e_ed_10.4.pdf-Mac-OS-X-Server
Apple-premiers_contacts_2e_ed_10.4
Apple-eMac_2005UserGuide
Apple-imacg5_20inch_Inverter
Apple-Keynote2_UserGuide.pdf-Japon
Apple-Welcome_to_Tiger.pdf-Japon
Apple-XsanAdminGuide_j.pdf-Japon
Apple-PowerBookG4_UG_15GE.PDF-Japon
Apple-Xsan_Migration.pdf-Japon
Apple-Xserve_Intel_DIY_TopCover_JA.pdf-Japon
Apple-iPod_nano_6thgen_User_Guide_J.pdf-Japon
Apple-Aperture_Photography_Fundamentals.pdf-Japon
Apple-nikeipod_users_guide.pdf-Japon
Apple-QuickTime71_UsersGuide.pdf-Japon
Apple-iMacG5_iSight_UG.pdf-Japon
Apple-Aperture_Performing_Adjustments_j.pdf-Japon
Apple-iMacG5_17inch_HardDrive.pdf-Japon
Apple-iPod_shuffle_Features_Guide_J.pdf-Japon
Apple-MacBook_Air_User_Guide.pdf-Japon
Apple-MacBook_UsersGuide.pdf-Japon
Apple-iPad_iOS4_Brukerhandbok.pdf-Norge-Norvege
Apple-Apple_AirPort_Networks_Early2009_H.pd-Norge-Norvege
Apple-iPod_classic_120GB_no.pdf-Norge-Norvege
Apple-StoreKitGuide.pdf-Japon
Apple-Xserve_Intel_DIY_ExpansionCardRiser_JA.pdf-Japon
Apple-iMacG5_Battery.pdf-Japon
Apple-Logic_Pro_8_Getting_Started.pdf-Japon
Apple-PowerBook-handbok-Norge-Norveg
Apple-iWork09_formler_og_funksjoner.pdf-Norge-Norvege
Apple-MacBook_Pro_15inch_Mid2010_H.pdf-Norge-Norvege
Apple-MacPro_HardDrive_DIY.pdf-Japon
Apple-iPod_Fifth_Gen_Funksjonsoversikt.pdf-Norge-Norvege
Apple-MacBook_13inch_white_Early2009_H.pdf-Norge-Norvege
Apple-GarageBand_09_Komme_i_gang.pdf-Norge-Norvege
Apple-MacBook_Pro_15inch_Mid2009_H.pdf-Norge-Norvege
Apple-imac_mid2011_ug_h.pdf-Norge-Norvege
Apple-iDVD_08_Komme_i_gang.pdf-Norge-Norvege
Apple-MacBook_Air_11inch_Late2010_UG_H.pdf-Norge-Norvege
Apple-iMac_Mid2010_UG_H.pdf-Norge-Norvege
Apple-MacBook_13inch_Mid2009_H.pdf-Norge-Norvege
/Apple-iPhone_3G_Viktig_produktinformasjon_H-Norge-Norvege
Apple-MacBook_13inch_Mid2010_UG_H.pdf-Norge-Norvege
Apple-macbook_air_13inch_mid2011_ug_no.pdf-Norge-Norvege
Apple-Mac_mini_Early2009_UG_H.pdf-Norge-Norvege
Apple-ipad2_brukerhandbok.pdf-Norge-Norvege
Apple-iPhoto_08_Komme_i_gang.pdf-Norge-Norvege
Apple-MacBook_Air_Brukerhandbok_Late2008.pdf-Norge-Norvege
Apple-Pages09_Brukerhandbok.pdf-Norge-Norvege
Apple-MacBook_13inch_Late2009_UG_H.pdf-Norge-Norvege
Apple-iPhone_3GS_Viktig_produktinformasjon.pdf-Norge-Norvege
Apple-MacBook_13inch_Aluminum_Late2008_H.pdf-Norge-Norvege
Apple-Wireless_Keyboard_Aluminum_2007_H-Norge-Norvege
Apple-NiPod_photo_Brukerhandbok_N0190269.pdf-Norge-Norvege
Apple-MacBook_Pro_13inch_Mid2010_H.pdf-Norge-Norvege
Apple-MacBook_Pro_17inch_Mid2010_H.pdf-Norge-Norvege
Apple-Velkommen_til_Snow_Leopard.pdf-Norge-Norvege.htm
Apple-TimeCapsule_Klargjoringsoversikt.pdf-Norge-Norvege
Apple-iPhone_3GS_Hurtigstart.pdf-Norge-Norvege
Apple-Snow_Leopard_Installeringsinstruksjoner.pdf-Norge-Norvege
Apple-iMacG5_iSight_UG.pdf-Norge-Norvege
Apple-iPod_Handbok_S0342141.pdf-Norge-Norvege
Apple-ipad_brukerhandbok.pdf-Norge-Norvege
Apple-GE_Money_Bank_Handlekonto.pdf-Norge-Norvege
Apple-MacBook_Air_11inch_Late2010_UG_H.pdf-Norge-Norvege
Apple-iPod_nano_6thgen_Brukerhandbok.pdf-Norge-Norvege
Apple-iPod_touch_iOS4_Brukerhandbok.pdf-Norge-Norvege
Apple-MacBook_Air_13inch_Late2010_UG_H.pdf-Norge-Norvege
Apple-MacBook_Pro_15inch_Early2011_H.pdf-Norge-Norvege
Apple-Numbers09_Brukerhandbok.pdf-Norge-Norvege
Apple-Welcome_to_Leopard.pdf-Japon
Apple-PowerMacG5_UserGuide.pdf-Norge-Norvege
Apple-iPod_touch_2.1_Brukerhandbok.pdf-Norge-Norvege
Apple-Boot_Camp_Installering-klargjoring.pdf-Norge-Norvege
Apple-MacOSX10.3_Welcome.pdf-Norge-Norvege
Apple-iPod_shuffle_3rdGen_UG_H.pdf-Norge-Norvege
Apple-iPhone_4_Viktig_produktinformasjon.pdf-Norge-Norvege
Apple_TV_Klargjoringsoversikt.pdf-Norge-Norvege
Apple-iMovie_08_Komme_i_gang.pdf-Norge-Norvege
Apple-iPod_classic_160GB_Brukerhandbok.pdf-Norge-Norvege
Apple-Boot_Camp_Installering_10.6.pdf-Norge-Norvege
Apple-Network-Services-Location-Manager-Veiledning-for-nettverksadministratorer-Norge-Norvege
Apple-iOS_Business_Mar12_FR.pdf
Apple-PCIDualAttachedFDDICard.pdf
Apple-Aperture_Installing_Your_Software_f.pdf
Apple-User_Management_Admin_v10.4.pdf
Apple-Compressor-4-ユーザーズマニュアル Japon
Apple-Network_Services_v10.4.pdf
Apple-iPod_2ndGen_USB_Power_Adapter-DE
Apple-Mail_Service_v10.4.pdf
Apple-AirPort_Express_Opstillingsvejledning_5.1.pdf
Apple-MagSafe_Airline_Adapter.pdf
Apple-L-Apple-Multiple-Scan-20-Display
Apple-Administration_du_service_de_messagerie_10.5.pdf
Apple-System_Image_Admin.pdf
Apple-iMac_Intel-based_Late2006.pdf-Japon
Apple-iPhone_3GS_Finger_Tips_J.pdf-Japon
Apple-Power-Mac-G4-Mirrored-Drive-Doors-Japon
Apple-AirMac-カード取り付け手順-Japon
Apple-iPhone開発ガイド-Japon
Apple-atadrive_pmg4mdd.j.pdf-Japon
Apple-iPod_touch_2.2_User_Guide_J.pdf-Japon
Apple-Mac_OS_X_Server_v10.2.pdf
Apple-AppleCare_Protection_Plan_for_Apple_TV.pdf
Apple_Component_AV_Cable.pdf
Apple-DVD_Studio_Pro_4_Installation_de_votre_logiciel
Apple-Windows_Services
Apple-Motion_3_New_Features_F
Apple-g4mdd-fw800-lowerfan
Apple-MacOSX10.3_Welcome
Apple-Print_Service
Apple-Xserve_Setup_Guide_F
Apple-PowerBookG4_17inch1.67GHzUG
Apple-iMac_Intel-based_Late2006
Apple-Installation_de_votre_logiciel
Apple-guide_des_fonctions_de_l_iPod_nano
Apple-Administration_de_serveur_v10.5
Apple-Mac-OS-X-Server-Premiers-contacts-Pour-la-version-10.3-ou-ulterieure
Apple-boot_camp_install-setup
Apple-iBookG3_14inchUserGuideMultilingual
Apple-mac_pro_server_mid2010_ug_f
Apple-Motion_Supplemental_Documentation
Apple-imac_mid2011_ug_f
Apple-iphone_guide_de_l_utilisateur
Apple-macbook_air_11inch_mid2011_ug_fr
Apple-NouvellesfonctionnalitesdeLogicExpress7.2
Apple-QT_Streaming_Server
Apple-Web_Technologies_Admin
Apple-Mac_Pro_Early2009_4707_UG
Apple-guide_de_l_utilisateur_de_Numbers08
Apple-Decouverte_d_Aperture_2
Apple-Guide_de_configuration_et_d'administration
Apple-mac_integration_basics_fr_106.
Apple-iPod_shuffle_4thgen_Guide_de_l_utilisateur
Apple-ARA_Japan
Apple-081811_APP_iPhone_Japanese_v5.4.pdf-Japan
Apple-Recycle_Contract120919.pdf-Japan
Apple-World_Travel_Adapter_Kit_UG
Apple-iPod_nano_6thgen_User_Guide
Apple-RemoteSupportJP
Apple-Mac_mini_Early2009_UG_F.pdf-Manuel-de-l-utilisateur
Apple-Compressor_3_Batch_Monitor_User_Manual_F.pdf-Manuel-de-l-utilisateur
Apple-Premiers__contacts_avec_iDVD_08
Apple-Mac_mini_Intel_User_Guide.pdf
Apple-Prise_en_charge_des_surfaces_de_controle_Logic_Express_8
Apple-mac_integration_basics_fr_107.pdf
Apple-Final-Cut-Pro-7-Niveau-1-Guide-de-preparation-a-l-examen
Apple-Logic9-examen-prep-fr.pdf-Logic-Pro-9-Niveau-1-Guide-de-preparation-a-l-examen
Apple-aperture_photography_fundamentals.pdf-Manuel-de-l-utilisateu
Apple-emac-memory.pdf-Manuel-de-l-utilisateur
Apple-Apple-Installation-et-configuration-de-votre-Power-Mac-G4
Apple-Guide_de_l_administrateur_d_Xsan_2.pdf
Apple-premiers_contacts_avec_imovie6.pdf
Apple-Tiger_Guide_Installation_et_de_configuration.pdf
Apple-Final-Cut-Pro-7-Level-One-Exam-Preparation-Guide-and-Practice-Exam
Apple-Open_Directory.pdf
Apple-Nike_+_iPod_User_guide
Apple-ard_admin_guide_2.2_fr.pdf
Apple-systemoverviewj.pdf-Japon
Apple-Xserve_TO_J070411.pdf-Japon
Apple-Mac_Pro_User_Guide.pdf
Apple-iMacG5_iSight_UG.pdf
Apple-premiers_contacts_avec_iwork_08.pdf
Apple-services_de_collaboration_2e_ed_10.4.pdf
Apple-iPhone_Bluetooth_Headset_Benutzerhandbuch.pdf
Apple-Guide_de_l_utilisateur_de_Keynote08.pdf
APPLE/Apple-Logic-Pro-9-Effectsrfr.pdf
Apple-Logic-Pro-9-Effectsrfr.pdf
Apple-iPod_shuffle_3rdGen_UG_F.pdf
Apple-iPod_classic_160Go_Guide_de_l_utilisateur.pdf
Apple-iBookG4GettingStarted.pdf
Apple-Administration_de_technologies_web_10.5.pdf
Apple-Compressor-4-User-Manual-fr
Apple-MainStage-User-Manual-fr.pdf
Apple-Logic_Pro_8.0_lbn_j.pdf
Apple-PowerBookG4_15inch1.67-1.5GHzUserGuide.pdf
Apple-MacBook_Pro_15inch_Mid2010_CH.pdf
Apple-LED_Cinema_Display_27-inch_UG.pdf
Apple-MacBook_Pro_15inch_Mid2009_RS.pdf
Apple-macbook_pro_13inch_early2011_f.pdf
Apple-iMac_Mid2010_UG_BR.pdf
Apple-iMac_Late2009_UG_J.pdf
Apple-iphone_user_guide-For-iOS-6-Software
Apple-iDVD5_Getting_Started.pdf
Apple-guide_des_fonctionnalites_de_l_ipod_touch.pdf
Apple_iPod_touch_User_Guide
Apple_macbook_pro_13inch_early2011_f
Apple_Guide_de_l_utilisateur_d_Utilitaire_RAID
Apple_Time_Capsule_Early2009_Setup_F
Apple_iphone_4s_finger_tips_guide_rs
Apple_iphone_upute_za_uporabu
Apple_ipad_user_guide_ta
Apple_iPod_touch_User_Guide
apple_earpods_user_guide
apple_iphone_gebruikershandleiding
apple_iphone_5_info
apple_iphone_brukerhandbok
apple_apple_tv_3rd_gen_setup_tw
apple_macbook_pro-retina-mid-2012-important_product_info_ch
apple_Macintosh-User-s-Guide-for-Macintosh-PowerBook-145
Apple_ipod_touch_user_guide_ta
Apple_TV_2nd_gen_Setup_Guide_h
Apple_ipod_touch_manual_del_usuario
Apple_iphone_4s_finger_tips_guide_tu
Apple_macbook_pro_retina_qs_th
Apple-Manuel_de_l'utilisateur_de_Final_Cut_Server
Apple-iMac_G5_de_lutilisateur
Apple-Cinema_Tools_4.0_User_Manual_F
Apple-Personal-LaserWriter300-User-s-Guide
Apple-QuickTake-100-User-s-Guide-for-Macintosh
Apple-User-s-Guide-Macintosh-LC-630-DOS-Compatible
Apple-iPhone_iOS3.1_User_Guide
Apple-iphone_4s_important_product_information_guide
Apple-iPod_shuffle_Features_Guide_F
Liste-documentation-apple
Apple-Premiers_contacts_avec_iMovie_08
Apple-macbook_pro-retina-mid-2012-important_product_info_br
Apple-macbook_pro-13-inch-mid-2012-important_product_info
Apple-macbook_air-11-inch_mid-2012-qs_br
Apple-Manuel_de_l_utilisateur_de_MainStage
Apple-Compressor_3_User_Manual_F
Apple-Color_1.0_User_Manual_F
Apple-guide_de_configuration_airport_express_4.2
Apple-TimeCapsule_SetupGuide
Apple-Instruments_et_effets_Logic_Express_8
Apple-Manuel_de_l_utilisateur_de_WaveBurner
Apple-Macmini_Guide_de_l'utilisateur
Apple-PowerMacG5_UserGuide
Disque dur, ATA parallèle Instructions de remplacement
Apple-final_cut_pro_x_logic_effects_ref_f
Apple-Leopard_Installationshandbok
Manuale Utente PowerBookG4
Apple-thunderbolt_display_getting_started_1e
Apple-Compressor-4-Benutzerhandbuch
Apple-macbook_air_11inch_mid2011_ug
Apple-macbook_air-mid-2012-important_product_info_j
Apple-iPod-nano-Guide-des-fonctionnalites
Apple-iPod-nano-Guide-des-fonctionnalites
Apple-iPod-nano-Guide-de-l-utilisateur-4eme-generation
Apple-iPod-nano-Guide-de-l-utilisateur-4eme-generation
Apple-Manuel_de_l_utilisateur_d_Utilitaire_de_reponse_d_impulsion
Apple-Aperture_2_Raccourcis_clavier
AppleTV_Setup-Guide
Apple-livetype_2_user_manual_f
Apple-imacG5_17inch_harddrive
Apple-macbook_air_guide_de_l_utilisateur
Apple-MacBook_Early_2008_Guide_de_l_utilisateur
Apple-Keynote-2-Guide-de-l-utilisateur
Apple-PowerBook-User-s-Guide-for-PowerBook-computers
Apple-Macintosh-Performa-User-s-Guide-5200CD-and-5300CD
Apple-Macintosh-Performa-User-s-Guide
Apple-Workgroup-Server-Guide
Apple-iPod-nano-Guide-des-fonctionnalites
Apple-iPad-User-Guide-For-iOS-5-1-Software
Apple-Boot-Camp-Guide-d-installation-et-de-configuration
Apple-iPod-nano-Guide-de-l-utilisateur-4eme-generation
Power Mac G5 Guide de l’utilisateur APPLE
Guide de l'utilisateur PAGE '08 APPLE
Guide de l'utilisateur KEYNOTE '09 APPLE
Guide de l'Utilisateur KEYNOTE '3 APPLE
Guide de l'Utilisateur UTILITAIRE RAID
Guide de l'Utilisateur Logic Studio
Power Mac G5 Guide de l’utilisateur APPLE
Guide de l'utilisateur PAGE '08 APPLE
Guide de l'utilisateur KEYNOTE '09 APPLE
Guide de l'Utilisateur KEYNOTE '3 APPLE
Guide de l'Utilisateur UTILITAIRE RAID
Guide de l'Utilisateur Logic Studio
Guide de l’utilisateur ipad Pour le logiciel iOS 5.1
PowerBook G4 Premiers Contacts APPLE
Guide de l'Utilisateur iphone pour le logiciel ios 5.1 APPLE
Guide de l’utilisateur ipad Pour le logiciel iOS 4,3
Guide de l’utilisateur iPod nano 5ème génération
Guide de l'utilisateur iPod Touch 2.2 APPLE
Guide de l’utilisateur QuickTime 7 Mac OS X 10.3.9 et ultérieur Windows XP et Windows 2000
Guide de l'utilisateur MacBook 13 pouces Mi 2010
Guide de l’utilisateur iPhone (Pour les logiciels iOS 4.2 et 4.3)
Guide-de-l-utilisateur-iPod-touch-pour-le-logiciel-ios-4-3-APPLE
Guide-de-l-utilisateur-iPad-2-pour-le-logiciel-ios-4-3-APPLE
Guide de déploiement en entreprise iPhone OS
Guide-de-l-administrateur-Apple-Remote-Desktop-3-1
Guide-de-l-utilisateur-Apple-Xserve-Diagnostics-Version-3X103
Guide-de-configuration-AirPort-Extreme-802.11n-5e-Generation
Guide-de-configuration-AirPort-Extreme-802-11n-5e-Generation
Guide-de-l-utilisateur-Capteur-Nike-iPod
Guide-de-l-utilisateur-iMac-21-5-pouces-et-27-pouces-mi-2011-APPLE
Guide-de-l-utilisateur-Apple-Qadministrator-4
Guide-d-installation-Apple-TV-3-eme-generation
User-Guide-iPad-For-ios-5-1-Software
Cocoa Bindings
Programming TopicsContents
Introduction to Cocoa Bindings Programming Topics 7
Who Should Read This Document 7
Organization of This Document 7
See Also 8
What Are Cocoa Bindings? 10
The Advantages of Using Bindings 10
The Model-View-Controller Design Pattern 11
What Is a Binding? 12
A Simple Example 12
Binding Options 14
Extending the MVC Design Pattern 15
Supporting Technologies 16
Key-Value Binding 16
Key-Value Coding 17
Key-Value Observing 18
Why Are NSControllers Useful? 19
NSController Classes 19
What Can You Bind? 20
Real-World Example 20
How Do Bindings Work? 23
Overview of the Supporting Technologies 23
The Supporting Technologies in Detail 25
Establishing Bindings with Key-Value Binding 26
NSEditor/NSEditorRegistration 26
Key-Value Coding 26
Key-Value Observing 27
Unbinding 29
Bindings in More Detail 29
Responding to Changes 32
View-Initiated Updates 32
Model-Initiated Updates 34
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
2Providing Controller Content 37
Setting the Content of a Controller 37
Traversing Tree Content with an NSTreeController 39
Specifying the Class of a Controller’s Content 40
Automatically Prepares Content 40
Programmatically Modifying a Controller’s Contents 41
Modifying Controller Content by Target-Action 42
De-coupling a Controller from its Content Bindings 44
Working With a Controller’s Selection 45
Getting a Controller’s Currently Selected Objects 45
Changing the Current Selection 46
Changing the Selection by Object 46
Getting and Setting Selection by Index 47
Setting the Selection Behaviors 48
Avoids Empty Selection 48
Selecting Objects Upon Insertion 49
Always Uses Multiple Values Marker 49
Preserves Selection 50
Selects All When Setting Content 50
Bindings Message Flow 52
Changing the Value of a Model Property 53
User Updates a Value in the User Interface 56
User Defaults and Bindings 61
What Is NSUserDefaultsController? 61
The Shared User Defaults Controller 61
Binding to the Shared User Defaults Controller 62
initialValues Versus NSUserDefaults registerDefaults: 62
Search Order for Defaults Values 64
Programmatically Accessing NSUserDefaultsController Values 64
Creating a Master-Detail Interface 65
What’s a Master-Detail Interface? 65
Creating Models 66
Creating Views 67
Creating Controllers 68
Binding Controllers to Models 69
Binding Views to Controllers 70
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
3Binding the Master Interface 70
Binding the Detail Interface 71
Displaying Images Using Bindings 73
Creating Models 73
Creating Views 74
Creating Controllers 74
Binding Views to Controllers 74
Using a Value Transformer to Convert Paths 75
Creating Custom Value Transformers 75
Registering Value Transformers 76
Binding Views to Controllers Using Transformers 76
Implementing To-One Relationships Using Pop-Up Menus 78
Creating Models and Controllers 78
Creating Views 79
Binding Views to Controllers 79
Filtering Using a Custom Array Controller 81
Overriding arrangeObjects: 81
Updating the Search String 82
Controller Key-Value Observing Compliance 83
NSUserDefaultsController 83
NSObjectController 83
NSArrayController 84
NSTreeController 84
Troubleshooting Cocoa Bindings 86
My collection controller isn’t displaying the current data 86
Changing the value in the user interface programmatically is not reflected in the model 86
Changing the value of a model property programmatically is not reflected in the user interface 87
Binding to the incorrect key path 87
The bindings of my custom view are disabled in Interface Builder 88
An uneditable/visible/disabled NSTextField becomes editable/hidden/enabled 89
Binding a control to a value that is an unsigned int causes an exception 89
The view bound to an NSTreeController is not displaying data 89
A table column containing NSPopUpButtonCell items is not sortable 89
Document Revision History 90
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
4Figures and Listings
What Are Cocoa Bindings? 10
Figure 1 Controllers provide glue code 11
Figure 2 A simple Cocoa application 12
Figure 3 Slider example using target-action 13
Figure 4 Slider demonstration using bindings 13
Figure 5 Displaying temperature using transformers 14
Figure 6 Typical bindings configuration using existing controller 15
Figure 7 Bindings established using key-value binding 16
Figure 8 Using key-value-coding to update values 17
Figure 9 Key-value observing—registering observers 18
Figure 10 Key-value observing—notification of observers 18
Figure 11 User interface for Combatants application 20
Figure 12 Combatant class 21
Figure 13 Combatants application managed by bindings 21
How Do Bindings Work? 23
Figure 1 Example drawing application 24
Figure 2 Bindings for example graphics application 24
Figure 3 The complete edit cycle 25
Figure 4 Key-value coding in Cocoa bindings 27
Figure 5 Key-value observing in Cocoa bindings 28
Figure 6 Bindings for a joystick’s angle in Interface Builder 29
Listing 1 Interface for the Joystick class 30
Listing 2 Partial implementation of the bind:toObject:withKeyPath:options method for the Joystick class
31
Listing 3 Update method for the Joystick class 33
Listing 4 Observing method for the Joystick class 35
Providing Controller Content 37
Figure 1 Master-detail interface with and without contentArrayForMultipleSelection 38
Working With a Controller’s Selection 45
Figure 1 Preserves selection example 50
Figure 2 Selects all when setting content example 51
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
5Bindings Message Flow 52
Figure 1 Message flow when modifying the value of a model object property 53
Figure 2 Message flow in response to the user changing a value in an NSTextField 57
Figure 3 Message flow in response to the user changing a value in an NSTextField, view-controller
validation 58
User Defaults and Bindings 61
Listing 1 Binding the userName defaults key to an NSTextField programmatically 62
Listing 2 Changing the initial values of the sharedUserDefaultsController instance 63
Creating a Master-Detail Interface 65
Figure 1 A master-detail interface 66
Figure 2 An object model 67
Figure 3 An array controller Attributes pane 68
Figure 4 An array controller Bindings pane 69
Figure 5 NSTableColumn Bindings pane 71
Displaying Images Using Bindings 73
Figure 1 Using an NSImageCell in an NSTableColumn 73
Figure 2 Bindings pane of an NSTableColumn 77
Listing 1 PathTransformer implementation file 75
Implementing To-One Relationships Using Pop-Up Menus 78
Figure 1 Using pop-up menus to represent to-one relationships 78
Filtering Using a Custom Array Controller 81
Listing 1 Filtering implementation of arrangeObjects: 81
Listing 2 Updating searchString 82
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
6Cocoa bindings is a collection of technologies you can use in your applications to fully implement a
Model-View-Controller paradigm where models encapsulate application data, views display and edit that data,
and controllers mediate between the two. Cocoa bindings reduces the code dependencies between models,
views and controllers, supports multiple ways of viewing your data, and automatically synchronizes views
when models change. Cocoa bindings provides extensible controllers, protocolsfor models and viewsto adopt,
and additions to classes in Foundation and the Application Kit. You can eliminate most of your glue code by
using bindings available in Interface Builder to connect controllers with models and views.
Who Should Read This Document
Cocoa bindings is ideal for developers writing new applications who have some familiarity with Cocoa, and
for developers of existing applications who want to simply clean up or eliminate their existing glue code. In
most cases, Cocoa bindings can be used to replace traditional Cocoa mechanisms such as target-action,
delegation, and some data source protocols. However, great care has been taken to ensure that both approaches
can be used side by side within the same application.
This document assumes that you have read Key-Value Coding Programming Guide , Key-Value Observing
Programming Guide and Value Transformer Programming Guide .
Important: Cocoa bindings is available to Cocoa Objective-C applications running OS X version 10.3 and
later.
Organization of This Document
The following articles cover key concepts in understanding how Cocoa bindings works:
●
"What Are Cocoa Bindings?" (page 10) describes the advantages that Cocoa bindings offers developers;
provides a brief summary of how they work; and what design patterns you should use to adopt the
technology.
●
"How Do Bindings Work?" (page 23) describes in detail the technologies supporting Cocoa bindings and
how they interact.
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
7
Introduction to Cocoa Bindings Programming
Topics●
"User Defaults and Bindings" (page 61) describes the role of the NSUserDefaultsController and how it
works with NSUserDefaults.
●
"Providing Controller Content" (page 37) describes how to set and modify the content of NSObjectController
and its subclasses.
●
"Working With a Controller’s Selection" (page 45) describes how to get a controller’s selection and change
the current selection.
●
"Bindings Message Flow" (page 52) illustrates the flow of messages between model, view and controller
objects in a bindings application.
These articles contain tasks that teach you how to use Cocoa bindings:
●
"Creating a Master-Detail Interface" (page 65) explains how to implement a basic master-detail interface
where a table view is used in the master interface to display a collection of objects, and other views used
in the detail interface to display the selected object in the collection.
●
"Displaying Images Using Bindings" (page 73) describes the various options when displaying images in
columns and contains an example of a custom value transformer.
●
"Implementing To-One Relationships Using Pop-Up Menus" (page 78) explains how to implement editable
to-one relationship as pop-up menus.
●
"Filtering Using a Custom Array Controller" (page 81) explains how to add a search field to the master
interface to filter the objects it displays.
●
"Controller Key-Value Observing Compliance" (page 83) details the properties for which the controller
classes provide key-value observing change notifications.
●
"Troubleshooting Cocoa Bindings" (page 86) describes a number of common problems encountered with
Cocoa bindings applications and provides methods for correcting the issues.
See Also
There are other technologies, not fully covered in this topic, that are fundamental to how bindings work. You
may want to read these topics if you want a better understanding of the underpinnings of Cocoa bindings, or
if you want to use these technologies independent from bindings. For example, this topic does not explain
how to use the methods defined in the key-value observing protocol. Refer to these documents for more
details:
● Developing Cocoa Applications Using Bindings: A Tutorial takes you through the steps of building the
familiar Currency Converter application using Cocoa bindings.
Introduction to Cocoa Bindings Programming Topics
See Also
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
8● Cocoa Bindings Reference enumerates the classes that support Cocoa bindings and provides descriptions
of the bindings for each class, along with the supported options and placeholders.
● Key-Value Coding Programming Guide covers all the features of the key-value coding protocol that allows
objects to indirectly access the properties of other objects.
● Key-Value Observing Programming Guide covers all the features of the key-value observing protocol that
allows objects to observe changes in other objects.
● Value Transformer Programming Guide describes how to use value transformers to convert values from
one type to another.
● Sort Descriptor Programming Topics describes how to use sort descriptors that specify how collections are
sorted.
Introduction to Cocoa Bindings Programming Topics
See Also
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
9In the simplest functional sense, the Cocoa bindings technology provides a means of keeping model and view
values synchronized without you having to write a lot of “glue code.” It allows you to establish a mediated
connection between a view and a piece of data, “binding” them such that a change in one is reflected in the
other.
This article describes what the technology offers and how it makes writing applications easier. It also introduces
the idea that rather than completely reimplementing an existing application to make use of bindings, you can
incorporate bindings in stages.
This article also describes on a conceptual level how Cocoa bindings work, and the design patterns you should
adopt. It gives a brief overview of the Model-View-Controller design pattern, and why it is beneficial. It then
gives a conceptual overview of how the main technologies that underpin Cocoa bindings—key-value coding,
key-value observing, and key-value binding—work, and how they inter-relate. The article finally explains the
role of the controller classes that Cocoa bindings provide and why you should use them.
"How Do Bindings Work?" (page 23) describes the supporting technologies in greater detail.
The Advantages of Using Bindings
The Cocoa bindings technology offers a way to increase the functionality and consistency of your application
while at the same time decreasing the amount of code you have to write and maintain. It takes care of most
aspects of user interface management for you by allowing you to off load the work of custom glue code onto
reusable pre-built controllers. It helps you build polished, easy-to-use applications that leverage object
relationships, provide sortable tables, and include intelligent selection management.
Typically you do not need to completely rewrite your application in order to adopt Cocoa bindings. For example,
it is likely that you can factor out User Preferences to be managed by Cocoa bindings without affecting the
rest of an application. You will find it easier to make use of Cocoa bindings if your application adopts the
recommended design patterns.
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
10
What Are Cocoa Bindings?The Model-View-Controller Design Pattern
Cocoa applications generally adopt the Model-View-Controller (MVC) design pattern. When you develop a
Cocoa application, you typically use model, view, and controller objects, each of which performs a different
function. Model objects represent data and are typically saved to a file or some other permanent data store.
View objects display model attributes. Controller objects act as go-betweens, to make sure that what a view
displays is consistent with the corresponding model value and that any updates a user makes to a value in a
view are propagated to the model. An understanding of the MVC design pattern is essential to fully understand
and leverage Cocoa bindings. If you need to know more, read “The Model-View-Controller Design Pattern.”
If you adopt the MVC design pattern, much of your application code is easier to reuse and extend—you can
reuse model and view classes in different applications. Much of the implementation of a controller object
consists of what is commonly referred to as “glue code.” Glue code is the code that keeps the model values
and views synchronized, and is unique to each application. It is typically tedious and cumbersome to write,
contributes little to the fundamental function of the application, but you must do it well to provide a good
user experience.
Figure 1 Controllers provide glue code
firstName = "Jo"
lastName = "Jackson"
Employee
glue
Document Window
First Jo
Last Jackson
Cocoa bindings replace much of the glue code with reusable controllers and provide an infrastructure that
allows you to connect the user interface with an application’s data.
Cocoa uses a number of terms that are commonly used in computer science. To avoid misunderstanding, they
are defined in the “Terminology”section of Key-Value Coding ProgrammingGuide with their particularmeaning
in the context of Cocoa bindings.
What Are Cocoa Bindings?
The Model-View-Controller Design Pattern
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
11What Is a Binding?
A binding is an attribute of one object that may be bound to a property in anothersuch that a change in either
one isreflected in the other. For example, the “value” binding of a text field might be bound to the temperature
attribute of a particular model object. More typically, one binding might specify that a controller object
“presents” a model object and another binding might specify that the value of a text field be tied to the
temperature property of the object presented by the controller.
Although the following examples concentrate on simple cases, bindings are not restricted to the display of
textual or numeric values. Among other things, a binding might specify the color in which text should be
displayed, whether a view is hidden or not, or what message and arguments should be sent when a button is
pressed.
A Simple Example
Take as an example a very simple application in which the valuesin a text field and a slider are keptsynchronized.
Consider first an implementation that does not use bindings. The text field and slider are connected directly
to each other using target-action, where each is the other’s target and the action is takeFloatValueFrom:
as shown in Figure 2. (If you do not understand this, you should read Getting Started With Cocoa.)
Figure 2 A simple Cocoa application
Window
Number 6.2
takeFloatValueFrom:
This example illustrates the dynamism of the Cocoa environment—the values of two user interface objects
are kept synchronized without writing any code, even without compiling. It also serves to illustrate the
target-action design pattern (for more details, read “The Target-Action Paradigm”).
What Are Cocoa Bindings?
What Is a Binding?
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
12The major flaw from which this example suffers is that, as it is, it has almost no real-world application. In order
to find out the value to which either the slider or the text field has been set, and update a model attribute
accordingly, you need connections to the text field and slider, and have to write some code. You typically use
a controller that is connected to both (using outlets) and to which both are connected (using target-action),
as illustrated in Figure 3 (page 13).
Figure 3 Slider example using target-action
Window
Number 6.2
slider = slider
textfield = textfield
number = number
Controller
number = "6.2"
MyObject
updateNumberFrom:
Instance variable setFloatValue:
When a user moves the slider, it sends an action message to its target (the controller). The controller in turn
updates the value in the model, and synchronizes the user interface (the text field and the slider). Although
this example is not particularly difficult, the situation becomes more complex if you use more complicated
models and displays, especially if you use, for example, table views that allow multiple selections, or if a value
may be displayed in a different window. And you have to write all the code to support this functionality.
Cocoa bindings uses prebuilt controller objects (subclasses of NSController) and supporting technologies to
keep values synchronized automatically. The application design for an implementation of the slider example
that uses bindings is shown in Figure 4.
Figure 4 Slider demonstration using bindings
Bind content
to object
Window
Number 6.2
number = "6.2"
MyObject
content = object
NSObjectController
Bind value to
object's number
What Are Cocoa Bindings?
What Is a Binding?
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
13Note that this implementation does not use the target-action pattern. The slider does not send an action
message to the controller. Instead, as the slider moves, it informs the controller directly that the value of its
content’s number has changed and what the value is. The controller updates the model and in turn informs
the text field and slider that the value they are displaying has changed. (In examples assimple asthis controllers
are not really necessary, however in most cases they are.) The mechanisms used to relay information are
explained later in this article and in greater detail in "How Do Bindings Work?" (page 23), but it is important
to appreciate that in most cases you will not have to write any glue code.
Binding Options
Many bindings allow you to specify options to customize their behavior. There are three types of option: value
transformers, placeholders, and other parameters.
A value transformer, as its name implies, applies a transformation to a value. A value transformer may also
allow reverse transformations. The Foundation framework provides the abstract NSValueTransformer class and
several convenient transformers, including one that negates a value—that is, it turns a Boolean YES into NO
(and vice versa). You can also implement your own transformers.
To see how transformers might be useful, suppose that in the previous example the number in the model
represents temperature in degrees Fahrenheit, but that you want to display the value in Celsius. You could
implement a reversible value transformer that converts values from one scale to the other. If you then specify
it as the transformer option for the text field and slider, as shown in Figure 5 (page 14), the user interface
displays the temperature in Celsius, and any new values entered using the slider or text field converted to
Fahrenheit.
Figure 5 Displaying temperature using transformers
WWiinnddooww
Bind content
to object
temperature = 77.0
MyObject
content = object
NSObjectController
Celsius 25.0
Bind value to
object's temperature
using Fahrenheit to
Celsius transformer
To learn more about transformers read Value Transformer Programming Guide (the article also shows an
implementation of the Fahrenheit to Celsius transformer).
What Are Cocoa Bindings?
What Is a Binding?
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
14Placeholder options allow you to specify what a view should display: if the value of the property to which it is
bound is null (nil); if there is no selection; if there is a multiple selection; or if for some other reason the value
is not applicable.
In addition to value transformers and placeholders, some bindings offer a variety of other options, such as
whether the value of the binding is updated as edits are made to the user interface item, or whether the
editable state of a user interface item is automatically configured based on the controller’s selection. For a
complete list of all the binding options available, see Cocoa Bindings Reference .
Extending the MVC Design Pattern
The Cocoa bindings architecture extends the traditional Cocoa MVC configuration, where there is a single
custom-built controller that manages the user interface. It provides a set of reusable controller classes that
inherit from an abstract superclass, NSController. In a bindings-based application there may be several
controllers—your own (such as an NSWindowController subclass, managing a document’s user interface) and
others that are subclasses of NSController and manage different parts of the user interface. You might also
create your own subclasses of the standard Application Kit controller classes—in particular you might subclass
NSArrayController to customize sorting and filtering behavior.
Other figures in this document present a convenient shorthand. Although the NSController instance is
conceptually bound directly to its model object, in most situations the binding will be “indirect,” to a variable
in your document object, as shown in Figure 6.
Figure 6 Typical bindings configuration using existing controller
WWiinnddooww
Celsius 25.0
Instance variable
temperature = "6.2"
MyObject
object = myObject
MyDocument
content = object
NSObjectController
Bind value to
selection's
temperature
Bind content to
controller.object
What Are Cocoa Bindings?
What Is a Binding?
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
15Supporting Technologies
Cocoa bindings rely primarily on two other technologies, key-value coding (KVC) and key-value observing
(KVO). Bindings themselves are established using key-value binding (KVB) as illustrated in Figure 7. In practice
you typically need to understand these technologies only if you want to create your own custom view with
bindings. If you want to use bindings, the only requirement that is imposed on you is that your model classes
must be compliant with key-value coding conventions for any properties to which you want to bind.
Key-Value Binding
A binding is established with a bind:toObject:withKeyPath:options: message which tells the receiver
to keep its specified attribute synchronized—modulo the options—with the value of the property identified
by the key path of the specified object. The receiver must watch for relevant changes in the object to which
it is bound and react to those changes. The receiver must also inform the object of changes to the bound
attribute. After a binding is established there are therefore two aspects to keeping the model and views
synchronized: responding to user interaction with views, and responding to changes in model values.
Figure 7 Bindings established using key-value binding
Establish
binding using
Key-Value
Binding
temperature = 77.0
MyObject
content = object
NSObjectController WWiinnddooww
Celsius 25.0
Establish
binding using
Key-Value
Binding
In a view-initiated update a value changed in the user interface is passed to the controller, which in turn pushes
the new value onto the model. To preserve the abstraction required to allow this to work with any controller
or model object, the system uses a common access protocol—key-value coding.
In a model-initiated update models notify controllers, and controllers notify views, of changes to values in
which interest has been registered using a common protocol—key-value observing. Note that a model-initiated
update can be triggered by direct manipulation of the model—for example by a Scripted Apple event—or as
the result of a view-initiated update—a change to the temperature made by editing the Celsius field must be
propagated back to the slider.
What Are Cocoa Bindings?
Supporting Technologies
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
16Key-Value Coding
Key-value coding is a mechanism whereby you can access a property in an object using the property’s name
as a string—the “key.” You can also use key paths to follow relationships between objects. For example, given
an Employee class with an attribute firstName, you could retrieve an employee’s first name using key-value
coding with the key firstName. If Employee has a relationship called “manager” to another Employee, you
could retrieve an employee’s manager’s first name using key-value coding with the key path
manager.firstName. For more details, see Key-Value Coding Programming Guide .
Recall that a binding specifies the key path to a property to which a given attribute is bound. If the value in
the slider or the text field is changed, it uses key-value coding—using the key path specified by the binding
as the key—to communicate that change directly to the controller, as illustrated in Figure 8. Note that the
arrows in this figure represent the direction in which messages are sent and in which information flows. The
new value is passed from the user interface widget to the controller, and from the controller to the model.
Figure 8 Using key-value-coding to update values
temperature = 77.0
MyObject
content = object
NSObjectController WWiinnddooww
Celsius 25.0
Update
temperature
using Key-Value
Coding
Update
values using
Key-Value
Coding
What Are Cocoa Bindings?
Supporting Technologies
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
17Key-Value Observing
Key-value observing is a mechanism whereby an object can register with another to be notified of changes to
the value of a property. When one object is bound to another object, it registers itself as an observer of the
relevant property of that object. In the current example, the text field and slider register as observers of the
temperature property of the controller’s content, as illustrated in Figure 9.
Figure 9 Key-value observing—registering observers
temperature = 77.0
MyObject
content = object
NSObjectController WWiinnddooww
Celsius 25.0
Register as an
observer of the
temperature
property
Register as an
observer of the
object
Note that the arrows shown in Figure 9 indicate direction of observation, not of data flow. Observation is a
“passive” process (akin to registering to receive notifications from an NSNotificationCenter). When a value
changes, the observed object sends a message to interested observers to notify them, as illustrated in Figure
10. The arrows in Figure 10 show the direction in which messages are sent.
Figure 10 Key-value observing—notification of observers
WWiinnddooww
temperature = 77.0
MyObject
content = object
NSObjectController
Celsius 25.0
Notify that
temperature
has changed
Notify that
temperature
has changed
What Are Cocoa Bindings?
Supporting Technologies
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
18Why Are NSControllers Useful?
Bindings can, in principle, be made between almost any two objects, provided that they are KVC-compliant
and KVO-compliant. A view could bind directly to a model object. Bindings-based applications, however, use
controller objects to manage individual model objects and collections of model objects and to interface to the
user preferences system.
It is possible to make bindings directly to your model objects or to controllers that do not inherit from
NSController—however you lose (or must reimplement) functionality provided by the Application Kit’s controller
objects.
● NSController instances manage their currentselection and placeholder values. This allows a view to display
an appropriate value if the controller’s selection is null, or if there is a multiple selection.
● NSController (and Application Kit user interface elements that support binding) implements the NSEditor
and NSEditorRegistration protocols. The NSEditorRegistration protocol provides a means for an editor (a
view) to inform a controller when it has uncommitted changes. The NSEditor protocol provides a means
for requesting that the receiver commit or discard any pending edits.
For example, if a user istyping in a text field and then clicks a button, the controller ensuresthat the model
object is updated with the complete contents of the text field before the button action takes place.
Although the methods are typically invoked on user interface elements by a controller they can also be
sent to a controller, for example in response to a user’s attempt to save a document or quit an application.
NSController Classes
NSController is an abstract class. Its concrete subclasses are NSObjectController, NSUserDefaultsController,
NSArrayController, and NSTreeController. NSObjectController manages a single object and provides the
functionality discussed so far. NSUserDefaultsController provides a convenient interface to the preferences
system.
NSArrayController and NSTreeController manage collections of model objects and track the current selection.
The collection controllers also allow you to add objects to, and remove objects from, the content collection.
The objects that the collection controllers manage don’t even have to be in an array—your container can
implement suitable methods (“indexed accessor” methods, defined in the NSKeyValueCoding protocol) to
present the values to the controller as if they were in an array.
What Are Cocoa Bindings?
Why Are NSControllers Useful?
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
19What Can You Bind?
You can make bindings for most of the Application Kit view classes, such as NSButton and NSTableView. Using
an array controller, for example, you can bind the contents of a pop-up menu to objects in an array. The
remainder of this article presents an example that is moderately complex. Although the details are intentionally
left vague it nevertheless serves to illustrate a number of points, and provides examples of more complex
bindings.
Real-World Example
Consider a game application in which the user manages a number of combatants, from which they can select
one as an attacker. A combatant can carry three weapons, one of which isselected at any time. In the application,
the list of combatants is shown in a table view, the window’s title shows the attacker’s name, and a pop-up
menu shows the currently selected weapon, as shown in Figure 11 (page 20).
Figure 11 User interface for Combatants application
Vlad
Selected Weapon
Dagger
Sword
Pike
Atilla
Attacker
Vlad
Doris
What Are Cocoa Bindings?
What Can You Bind?
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
20Combatants are represented by instances of the Combatant class. In the Combatant class, each weapon is
referenced as a separate instance variable, as shown in Figure 12. By implementing suitable “indexed” accessor
methods(defined by the key-value coding protocol), however, the Combatant class can allow an array controller
to access the weapons as if they were in an array.
Figure 12 Combatant class
weapon1 = dagger
weapon2 = sword
weapon3 = pike
selectedWeapon = weapon2
name = "Vlad"
Combatant
When the user chooses an attacker from the table view, the window title is updated to reflect the attacker’s
name, and the title of the pop-up menu is updated to reflect the attacker’s selected weapon. When the user
activates the pop-up its contents are created dynamically from the set of weapons carried by the combatant.
When the user selects a menu item, the combatant’s selected weapon is set to that corresponding to that
menu item. If a different attacker is selected, the pop-up, selection and window title update accordingly.
Figure 13 (page 21) illustrates how the user interface of the Combatants application can be implemented
using bindings. The table view is bound to an array controller that manages an array of combatants. The
window title is bound to the name of the selected combatant. The pop-up menu retrieves its list of items from
an array controller bound to the attacker’s weapons“array,” and itsselection is bound to the attacker’sselected
weapon.
Figure 13 Combatants application managed by bindings
contentArray = combatants
NSArrayController
contentArray =
selection.weapons
NSArrayController
Vlad
Selected Weapon
Dagger
Sword
Pike
Atilla
Attacker
Vlad
Doris
weapon1 = dagger
weapon2 = sword
weapon3 = pike
selectedWeapon = weapon2
name = "Vlad"
Combatant
selection
Bind title to
selection.name
Bind value to
arrangedObjects.name
Bind
contentArray to
selection.weapons
Bind
content to
arrangedObjects
Bind selectedObject to
selection.selectedWeapon
What Are Cocoa Bindings?
Real-World Example
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
21This example illustrates a number of points:
●
In an application you can use more than one controller object.
● Different aspects of a user interface element may be bound to different controllers.
● You can use your own custom model classes.
Finally, it should be emphasized that the example requires no actual code to set up the user interface—the
controllers and bindings can all be created in Interface Builder. This represents a considerable reduction in
programming effort compared with the traditional target-action based approach.
What Are Cocoa Bindings?
Real-World Example
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
22This article provides a conceptual explanation of how Cocoa bindings work. It describes:
● How connections between model and controller, and controller and view, are established using key-value
binding
● Unbinding
● The NSEditor and NSEditorRegistration protocols
● The technologies that Cocoa bindings use to support communication between the model, view, and
controller, namely key-value coding, and key-value observing
● How the various technologies interact
You should already be familiar with the concepts presented in "What Are Cocoa Bindings?" (page 10).
Overview of the Supporting Technologies
This section presents an overview of the technologies that make Cocoa bindings work and how they interact.
They are discussed in greater detail in the following sections.
Cocoa bindings rely on other technologies—key-value coding (KVC) and key-value observing (KVO)—to
communicate changes between objects, and on key-value binding (KVB) to bind a value in one object to a
property in another. Cocoa bindings also use two protocols—NSEditor and NSEditorRegistration—that help
to ensure that any pending edits are either discarded or committed before user interface elements are disposed
of.
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
23
How Do Bindings Work?To understand how these technologies work together, consider a drawing application that allows the user to
draw graphic objects such as circles and rectangles on screen. Among other properties, a graphic object has
a shadow that may be offset a variable distance from the center of the graphic at an arbitrary angle. An inspector
displays the offset and angle of the selected graphic object’s shadow in a pair of text fields and a custom
view—a joystick—as shown in Figure 1.
Figure 1 Example drawing application
Shadow
Offset: 15.0
Angle: 28.00
+
Document
Joystick
The implementation of the inspector is illustrated in Figure 2. Both the text fields and the joystick are bound
to the selection of an NSArrayController. The controller’s contentArray is bound to an array of Graphic
objects. A Graphic hasinstance variablesto represent itsshadow’s angle in radians and its offset from its center.
Figure 2 Bindings for example graphics application
shadowOffset = 15.00
shadowAngle = 0.86
Graphic
selection = graphic
NSArrayController
Shadow
Offset: 15.0
Angle: 28.00
value bound to
selection.shadowOffset
value bound to
selection.shadowAngle
using radians to degrees
value transformer
selection
+
offset bound to
selection.shadowOffset
angle bound to
selection.shadowAngle
using radians to degrees
value transformer
How Do Bindings Work?
Overview of the Supporting Technologies
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
24The text fields’ values are bound to the angle and offset of the graphic object’s shadow; the joystick provides
a graphical representation of angle and offset. The angle in the text field is displayed in degrees, and the angle
used internally by the joystick is specified in radians. The bindings for both of these specify a reversible value
transformer that converts between radians and degrees.
The complete sequence of events that occurs when a user edits a value in the angle text field is illustrated in
Figure 3 (page 25).
Figure 3 The complete edit cycle
shadowOffset = 15.00
shadowAngle = 0.86
Graphic
selection = graphic
NSArrayController
Shadow
Offset: 15.0
Angle: 28.00
+
+
KVC + degrees
to radians
transformation
KVC
KVO
2
3
1
4
+
KVO + degrees
to radians
transformation
1. The user enters a new value in the Angle text field. The text field uses the NSEditorRegistration protocol
to indicate that an edit has begun, and when it is complete. The text field’s binding specifies a reversible
radians-to-degrees value transformer, so the new value is converted to radians.
2. Using KVC, through the controller the view updates the model object’s shadowAngle variable.
3. Through KVO, the model informsthe controller that an update has been made to its shadowAngle variable.
4. Through KVO, the controller informs the joystick and the angle text field that an update has been made
to its content’s shadowAngle variable.
Notice that the Offset text field was not involved in the cycle in any way. Cocoa bindings impose no more
overhead than is necessary.
The next sections explain in greater detail how bindings are established and how the underlying technologies
operate.
The Supporting Technologies in Detail
This section first describes the technologies that support bindings and shows how they play their parts. It also
explains what steps you must take in order to take advantage of these technologies.
How Do Bindings Work?
The Supporting Technologies in Detail
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
25Establishing Bindings with Key-Value Binding
Key-value binding is used to establish bindings. The NSKeyValueBindingCreation informal protocol declares
methods to establish and remove bindings between objects. In addition, it provides a means for a class to
advertise the bindings that it exposes.
In most cases you need to use bind:toObject:withKeyPath:options:, and then only when you establish
bindings programatically. Use of the unbind: is discussed in “Unbinding.” The other methods—the class
method exposeBinding: and the instancemethods exposedBindings and valueClassForBinding:—are
useful only in an Interface Builder palette.
NSEditor/NSEditorRegistration
Together the NSEditorRegistration and NSEditor protocols allow views to notify a controller that an edit is
underway and to ensure that any pending edits are committed as and when necessary.
The NSEditorRegistration informal protocol isimplemented by controllersto provide an interface for a view—the
editor—to inform the controller when it has uncommitted changes. When an edit is initiated, the view sends
the controller an objectDidBeginEditing: message. When the edit is complete (for example when the
user presses Return) the view sends an objectDidEndEditing: message.
The controller is responsible for tracking which editors have uncommitted changes and requesting that they
commit or discard any pending edits when appropriate—for example, if the user closes the window or quits
the application. The request takes the form of a commitEditing or discardEditing message, defined by
the NSEditor informal protocol. NSController provides an implementation of this protocol, as do the Application
Kit user interface elements that support binding.
Key-Value Coding
Key-value coding (KVC) provides a unified way to access an object’s properties by name (key) without requiring
use of custom accessor methods. The NSKeyValueCoding protocol specifies among others two methods,
valueForKey: and setValue:forKey:, that give access to an object’s property with a specified name. In
addition, the methods setValue:forKeyPath: and valueForKeyPath: give access to properties across
relationships using key paths of the form relationship.property , for example, content.lastName.
How Do Bindings Work?
The Supporting Technologies in Detail
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
26A binding for a given property specifies an object and a key path to a property of that object. Given this
information, the bound object can use key-value coding—specifically setValue:forKeyPath:—to update
the object to which it is bound without having to hard-code an accessor method, as illustrated in Figure 4.
Figure 4 Key-value coding in Cocoa bindings
shadowOffset = 15.00
shadowAngle = 0.86
Graphic
selection = graphic
NSArrayController Shadow
Offset: 15.0
Angle: 28.00
+
+
setValue:
forKeyPath:
setValue:
forKeyPath:
Update to value in
controller passed
to model using KVC
Update to value
in view passed to
controller using KVC
+
+
Since Cocoa bindings rely on KVC, your model and controller objects must be KVC-compliant for other objects
to be able to bind to them. To support KVC you simply have to follow a set of conventions for naming your
accessor methods, briefly summarized here:
● For an attribute or to-one relationship named , implement methods named and, if the
attribute is read-write, set:—the case is important.
● For a to-many relationship implement either a method named or both countOf and
objectInAtIndex:. The latter pair is useful if the related objects are not stored in an array. It is
up to you to retrieve an appropriate value for the specified index—it does not matter how the value is
derived. For a mutable to-many relationship, you should also implement either set (if you
implemented ) or insertObject:inAtIndex: and removeObjectFromAtIndex:.
For full details of all the methods declared by the NSKeyValueCoding protocol, see Key-Value Coding
Programming Guide .
Key-Value Observing
KVO is a mechanism that allows an object to register to receive notifications of changes to values in other
objects. To register, an observer sends the object it wants to observe an
addObserver:forKeyPath:options:context: message that specifies:
● The object that is observing (very often self)
● The key path to observe (for example, selection.name)
How Do Bindings Work?
The Supporting Technologies in Detail
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
27● What information will be sent on notification (for example, old value and new value)
● Optionally, contextual information to be sent back on notification (for example, a flag to indicate what
binding is being affected)
An observed object communicates changes directly to observers by sending them an
observeValueForKeyPath:ofObject:change:context: message (defined by the NSKeyValueObserving
informal protocol)—there is no independent notifier object akin to NSNotificationCenter. Figure 5 illustrates
how a change to a model value (the shadow angle) is communicated to views using KVO.
Figure 5 Key-value observing in Cocoa bindings
shadowOffset = 15.00
shadowAngle = 0.49
Graphic
selection = graphic
NSArrayController Shadow
Offset: 15.0
Angle: 28.00
+
+
observeValueForKeyPath:
ofObject:change:context
Update to value in
model passed to
controller using KVO
+
observeValueForKeyPath:
ofObject:change:context
Update to value in
controller passed
to views using KVO
The message is sent to observers for every change to an observed property, independently of how the change
wastriggered. (Note that the protocol itself makes no assumptions about what the observer actually does with
the information.)
One other important aspect of KVO is that you can register the value of a key as being dependent on the value
of one or more others. A change in the value associated with one of the “master” keys triggers a change
notification for the dependent key’s value. For example, the drawing bounds of a graphic object may be
dependent on the offset and angle of the associated shadow. If either of those values changes, any objects
observing the drawing bounds must be notified.
The main requirement to make use of KVO is that your model objects be KVO-compliant. In most cases this
actually requires no effort—the runtime system adds support automatically. By default, all invocations of KVC
methods result in observer notifications. You can also implement manual support—see Key-Value Observing
Programming Guide for more details.
How Do Bindings Work?
The Supporting Technologies in Detail
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
28Unbinding
Typically the only reason you would explicitly unbind an object isif you modify the user interface programatically
and want to remove a binding. If you change an objects binding’s values it should first clear any preexisting
values.
If you implement a custom view or controller with custom bindings, you should ensure that it clears any
bindings before it is deallocated—in particular itshould release any objectsthat were retained for the specified
binding in the bind:toObject:withKeyPath:options: method and should deregister as an observer of
any objects for which it registered as an observer. It may be convenient to implement this logic in an unbind:
method that you then call as the first step in dealloc.
Bindings in More Detail
This section examines bindings in more detail. It decomposes—from the perspective of a custom view, a
joystick—what happens when a binding is established. This serves two purposes: it makes explicit what code
is involved in establishing a binding and responding to changes, and it gives a conceptual overview of how
to create your own bindings-enabled views.
A binding specifies what aspect of one objectshould be bound to what property in another,such that a change
in either is reflected in the other. For a given binding, an object therefore records the target object for the
binding, the associated key path, and any options that were specified.
You can establish bindings easily in Interface Builder using the Bindings pane of the Info window. In Interface
Builder, the angle binding for a joystick that displays the offset and angle of graphics object’s shadow might
look like Figure 6.
Figure 6 Bindings for a joystick’s angle in Interface Builder
Binding name
Object
Key path
Options
How Do Bindings Work?
Bindings in More Detail
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
29Establishing this binding in Interface Builder is equivalent to programatically sending this message to the
joystick:
[joystick bind:@"angle" toObject:GraphicController
withKeyPath:@"selection.shadowAngle" options:options];
The arguments have the following meanings:
@"angle"
Identifies an attribute whose value can be bound to the value of a property in another object. Note that
the binding name need not necessarily correspond to the name of an actual instance variable.
GraphicController
The object containing the bound-to property.
@"selection.shadowAngle"
The key path that identifies the bound-to property.
options
A dictionary that specifies any options such as placeholders, or in this case, a value transformer.
The information defined by the arguments can be stored in the bound object (in this case the joystick) as
instance variables, as discussed next.
This example, and those that follow, assume that the joystick is represented by the class Joystick with instance
variables as defined in the interface shown in Listing 1.
Listing 1 Interface for the Joystick class
@interface Joystick : NSView
{
float angle;
id observedObjectForAngle;
NSString *observedKeyPathForAngle;
NSValueTransformer *angleValueTransformer;
// ...
}
In its bind:toObject:withKeyPath:options: method an object must as a minimum do the following:
● Determine which binding is being set
How Do Bindings Work?
Bindings in More Detail
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
30● Record what object it is being bound to using what keypath and with what options
● Register as an observer of the keypath of the object to which it is bound so that it receives notification of
changes
The code sample in Listing 2 shows a partial implementation of Joystick’s
bind:toObject:withKeyPath:options: method dealing with just the angle binding.
Listing 2 Partial implementation of the bind:toObject:withKeyPath:options method for the Joystick class
static void *AngleBindingContext = (void *)@"JoystickAngle";
- (void)bind:(NSString *)binding
toObject:(id)observableObject
withKeyPath:(NSString *)keyPath
options:(NSDictionary *)options
{
// Observe the observableObject for changes -- note, pass binding identifier
// as the context, so you get that back in observeValueForKeyPath:...
// This way you can easily determine what needs to be updated.
if ([binding isEqualToString:@"angle"])
{
[observableObject addObserver:self
forKeyPath:keyPath
options:0
context:AngleBindingContext];
// Register what object and what keypath are
// associated with this binding
observedObjectForAngle = [observableObject retain];
observedKeyPathForAngle = [keyPath copy];
// Record the value transformer, if there is one
angleValueTransformer = nil;
NSString *vtName = [options objectForKey:@"NSValueTransformerName"];
if (vtName != nil)
How Do Bindings Work?
Bindings in More Detail
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
31{
angleValueTransformer = [NSValueTransformer
valueTransformerForName:vtName];
}
}
// Implementation continues...
This partial implementation does not record binding options other than a value transformer (although it may
simply be that the binding does not allow for them). It neverthelessillustratesthe basic principles of establishing
a binding. Notice in particular the contextual information passed in the
addObserver:forKeyPath:options:context: message; this is returned in the
observeValueForKeyPath:ofObject:change:context: method and can be used to determine which
binding is affected by the value update.
Responding to Changes
As noted earlier, there are two aspects to change management—responding to view-initiated changes that
must be propagated ultimately to the model, and responding to model-initiated changesthat must be reflected
in the view. This section illustrates both, and shows how KVC and KVO play their parts.
View-Initiated Updates
Recall that the joystick was bound to the controller with the following method:
[joystick bind:@"angle" toObject:GraphicController
withKeyPath:@"selection.shadowAngle" options:options];
From the perspective of view-initiated updates the method can be interpreted as follows:
bind: @"angle"
If whatever is associated with angle changes,
toObject: GraphicController
tell the specified object (GraphicController) that
withKeyPath: @"selection.shadowAngle"
the value of its (the GraphicController’s) selection.shadowAngle has changed
How Do Bindings Work?
Responding to Changes
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
32options: options
using any of these options that may be appropriate.
If the value associated with angle changes—typically when a user clicks or drags the mouse within the
view—the joystick should pass the new value to the controller using KVC, as illustrated in Figure 4. The joystick
should therefore respond to user input as follows:
● Determine new values for angle and offset
● Update its own display as appropriate
● Communicate new values to the controller to which it is bound
Listing 3 shows a partial implementation of an update method for the Joystick class. The excerpt deals just
with the angle binding. It illustrates the use of key-value coding to communicate the new value (transformed
by the value transformer if appropriate) to the observed object.
Listing 3 Update method for the Joystick class
-(void)updateForMouseEvent:(NSEvent *)event
{
float newAngleDegrees;
// calculate newAngleDegrees...
[self setAngle:newAngleDegrees];
if (observedObjectForAngle != nil)
{
NSNumber *newControllerAngle = nil;
if (angleValueTransformer != nil)
{
newControllerAngle =
[angleValueTransformer reverseTransformedValue:
[NSNumber numberWithFloat:newAngleDegrees]];
}
else
{
newControllerAngle = [NSNumber numberWithFloat:newAngleDegrees];
How Do Bindings Work?
Responding to Changes
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
33}
[observedObjectForAngle setValue: newControllerAngle
forKeyPath: observedKeyPathForAngle];
}
// ...
}
Note that this example omits several important details, such as editor registration and checking that the value
transformer allows reverse transformations.
Model-Initiated Updates
Recall again that the joystick was bound to the controller with the following method:
[joystick bind:@"angle" toObject:GraphicController
withKeyPath:@"selection.shadowAngle" options:options];
From the perspective of model-initiated updates the method can be interpreted as follows:
toObject: GraphicController
If the GraphicController’s
withKeyPath:@"selection.shadowAngle"
selection.shadowAngle changes
bind:@"angle"
update whatever is associated with the exposed angle key
options:options
using the options specified (for example, using a value transformer).
The receiver therefore registered as an observer of the specified object’s key path (selection.shadowAngle)
in its bind:toObject:withKeyPath:options: method, as wasshown in Listing 2. Observed objects notify
their observers by sending them an observeValueForKeyPath:ofObject:change:context: message.
Listing 4 shows a partial implementation for the Joystick class for handling the observer notifications that
result.
The fundamental requirement of the observeValueForKeyPath:ofObject:change:context: method
is that the value associated with the relevant attribute is updated. This excerpt also shows how it can capture
placeholder information that might be used in the display method to give visual feedback to the user, in this
case using an instance variable that indicates that for some reason the angle is “bad.”
How Do Bindings Work?
Responding to Changes
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
34Listing 4 Observing method for the Joystick class
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// You passed the binding identifier as the context when registering
// as an observer--use that to decide what to update...
if (context == AngleObservationContext)
{
id newAngle = [observedObjectForAngle
valueForKeyPath:observedKeyPathForAngle];
if ((newAngle == NSNoSelectionMarker) ||
(newAngle == NSNotApplicableMarker) ||
(newAngle == NSMultipleValuesMarker))
{
badSelectionForAngle = YES;
}
else
{
badSelectionForAngle = NO;
if (angleValueTransformer != nil)
{
newAngle = [angleValueTransformer
transformedValue:newAngle];
}
[self setValue:newAngle forKey:@"angle"];
}
}
// ...
[self setNeedsDisplay:YES];
}
How Do Bindings Work?
Responding to Changes
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
35In most controls the display method alters the visual representation depending on the current selection.
How Do Bindings Work?
Responding to Changes
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
36Controllers require content to manipulate and there are a number of options for setting this content. It can be
done programmatically, through bindings, or automatically in response to actions configured in Interface
Builder. This article describes the various methods of setting and modifying a controller’s content.
Setting the Content of a Controller
NSObjectController and its subclasses are initialized with the method initWithContent:, passing a content
object or nil if you intend to use the content bindings. You can explicitly set the content of an existing
controller using the setContent: method. It is far more common to provide content for controllers by
establishing a binding to one of their exposed Controller Content bindings.
NSObjectController exposes a single binding for content called contentObject. You can establish a binding
from contentObject to any object that is key-value-coding and key-value-observing compliant for the keys
that you intend to have the controller operate on.
The collection controllers expose additional bindings: contentArray, contentSet, and
contentArrayForMultipleSelection.
The contentArray binding is bound to an NSArray or an object that implementsthe appropriate array indexed
accessor methods. Similarly the contentSet binding is bound to an NSSet object or an object that implements
the appropriate set indexed accessor methods. The indexed accessor patterns are described in Indexed Accessor
Patterns for To-Many Properties in the Key-Value Coding Programming Guide .
The contentArrayForMultipleSelection bindingsis a special binding thatis enabled only after establishing
the contentArray or contentSet binding. The contentArrayForMultipleSelection binding is used
as a fallback for the content of the controller when the contentArray or contentSet bindings return the
multiple values marker. It allows you to use a different object and key path as the collection content in these
cases and is often used when implementing a master-detail style interface.
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
37
Providing Controller ContentFor example, Figure 1 shows a typical master-detail interface. The array controller that provides the list of
activities is designated as the master controller and the names of the activities are displayed in the table view
on the left. A second array controller is the detail controller and provides the names of members displayed in
the table view on the right.
Figure 1 Master-detail interface with and without contentArrayForMultipleSelection
When only the contentArray is bound to
selection.members the detail
table view is empty when the master array
controller has a multiple selection.
When the contentArrayForMultipleSelection
binding is also bound to
selection.@distinctUnionOfArrays.members
the detail table view displays the members
in all the selected activities.
The detail array controller’s contentArray binding is bound to the master array controller object with the
key path selection.members. The value binding of the column in the detail table view is bound to the
detail array controller’s arrangedObjects.name key path. When a single activity is selected in the master
table view, the detail table view displays the names of the to-many members relationship.
However, what happens if the master table view is configured to allow multiple selection? If only the detail
array controller’s contentArray is bound, the detail table view is empty. While thisislogical, it isn’t necessarily
the desired results. A better option might be to display a unique list of the members in the selected activities.
This is where the contentArrayForMultipleSelection binding and the key-value coding collection
operators come into play.
By establishing a binding from contentArrayForMultipleSelection to the master array controller using
the key path selection.@distinctUnionOfArrays.members, the detail table view will be populated
with the names of the users in all the selected activities. Because the @distinctUnionOfArrays operator
was used, members that are common to both activities do not appear as duplicate names in the detail table
view.
Providing Controller Content
Setting the Content of a Controller
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
38The contentArrayForMultipleSelection binding expects an array of data; it is not directly compatible
with set collections that are bound to the contentSet binding. You must use the @distinctUnionOfSets
or @unionOfSets set operator to convert the set to an array. The collection operators are described in “Set
and Array Operators” in the Key-Value Coding Programming Guide .
Note that when the master array controller has a multiple selection the detail array controller’s add and remove
buttons are disabled. The buttons’ enabled bindings are bound to the detail array controller’s canAdd and
canRemove methods. The detail array controller automatically knowsthat it is unable to add and remove items
to the composite array and updates the canAdd and canRemove state, causing the buttons to be disabled.
Traversing Tree Content with an NSTreeController
An NSTreeController requires that you describe how it should traverse the tree of objects, by specifying a child
key path. This key path can be set programmatically using theNSTreeControllermethod setChildrenKeyPath:
or specified in the tree controller’s inspector panel in Interface Builder.
All child objects for the tree must be key-value-coding compliant for the same child key path. If necessary you
should implement accessor methods in your model classes, or categories on those classes, that map the child
key to the appropriate class-specific method name.
An optional count key path can be specified that, if provided, returns the number of child objects available.
The count key path is set programmatically using the setCountKeyPath: method, or in the controller’s
inspector panel in Interface Builder. Your model objects are expected to update the value of the count key
path in a key-value-observing compliant method.
Warning: Providing the count key path to an NSTreeController instance disablesthe add:, addChild:,
remove:, removeChild:, or insert: methods.
You can optionally provide a leaf key path that specifies a key in your model object that returns YES if the
object is a leaf node, and NO if it is not. Providing this key path prevents the NSTreeController from having to
determine if a child object is a leaf node by examining the child object and as a result improve performance.
The leaf key path isset programmatically using the setLeafKeyPath: method, or in the controller’sinspector
panel in Interface Builder. This key path affects how an NSOutlineView or NSBrowser bound to the tree controller
displays disclosure triangles:
●
If the leaf key path for the model object returns YES, the outline view or browser does notshow a disclosure
triangle.
●
If the leaf key path for the model object returns NO, then it always shows the disclosure triangle.
Providing Controller Content
Traversing Tree Content with an NSTreeController
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
39●
If no leaf key path is configured for the controller, then the count or child key path of the model is queried
to determine how many child objects are present. If there are 0 child objects, the disclosure triangle is not
displayed, otherwise it is.
Specifying the Class of a Controller’s Content
In order for a controller to create new content objects automatically or in response to the target-action methods,
it must know the appropriate class to use.
Controllers can be configured in one of two modes: object mode or entity mode. In object mode the content
class is specified by the method setObjectClass: or in the controller inspector panel in Interface Builder.
If the controller is configured in entity mode, the class is determined by the name of the entity or by the
relationship that the entity definesfor the key. The entity name isset using setEntityName: or in the controller
inspector panel in Interface Builder.
If the controller is in object mode, the method newObject is used to create new objects. The default
implementation simply allocates a new object of the class specified by objectClass or the entityName and
sends the object a standard init message with no arguments. If your content objects require more complex
initialization, you can subclass the appropriate controller class and override the newObject method.
An NSObjectController expects the content object to be of the class specified by the object class or entity
name. When using the NSArrayController and NSTreeController the object classrefersto the individual content
objects, rather than the collection that holds the objects. In both cases the collections are expected to be
key-value-coding compatible with arrays or sets, depending on the binding providing the content for the
controller.
You are not restricted to having content of a single object class. You can create and insert objects of any class
using one of the programmatic manipulation methods discussed in "Programmatically Modifying a Controller’s
Contents" (page 41).
Automatically Prepares Content
NSObjectController and its classes provide support for automatically creating content for a controller when it
is loaded from a nib file. This is typically configured in the controller inspector in Interface Builder by enabling
the “Automatically Prepares Content” option. When this option is enabled, the controller creates and populates
the content object or content collection when the controller is loaded from a nib file by calling the controller’s
prepareContent method.
Providing Controller Content
Specifying the Class of a Controller’s Content
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
40For example, when an NSObjectController that has an object class of NSMutableDictionary is loaded from a
nib and automatically prepares content is selected, the content of the object controller will be set to a newly
instantiated, empty NSMutableDictionary instance.
Similarly, if an NSArrayController that has an object class of Activity is loaded, the content is set to a newly
instantiated NSMutableArray containing a single instance of Activity. NSTreeController acts the same way.
If the controller that is loaded is in entity mode, then the data corresponding to the entity name is fetched
and is filtered using the controller’s configured filter predicate.
Programmatically Modifying a Controller’s Contents
When you modify a controller’s content object the only restriction isthat you must do it in a key-value-observing
compliant manner so that the controller is informed of the changes. NSObjectController and its subclasses
provide a number of methods that allow you to modify the contents of a controller programmatically.
NSObjectController offers the addObject: and removeObject: methods. When used with an
NSObjectController, they are synonymous with the setContent: method, passing the parameter object or
nil respectively.
The addObject: and removeObject: methods have somewhat different behavior for NSArrayController. In
this case their behavior isthe same as NSArray's addObject: and removeObject: methods. Unlike NSArray’s
implementations, these methods inform the array controller of the changes so that they can be reflected in
the user interface.
NSArrayController extends the basic add and remove functionality with the following methods:
- (void)addObjects:(NSArray *)objects;
- (void)removeObjects:(NSArray *)objects;
- (void)removeObjectsAtArrangedObjectIndexes:(NSIndexSet *)indexes;
- (void)removeObjectAtArrangedObjectIndex:(unsigned int)index;
- (void)insertObjects:(NSArray *)objects atArrangedObjectIndexes:(NSIndexSet
*)indexes;
- (void)insertObject:(id)object atArrangedObjectIndex:(unsigned int)index;
Providing Controller Content
Programmatically Modifying a Controller’s Contents
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
41The addObjects: and removeObjects: methods add or remove the objects passed as parameters from
the collection. The method removeObjectsAtArrangedObjectIndexes: method iterates through the
passed indexes, removing each object from the collection. The method
removeObjectAtArrangedObjectIndex: removes the single object at the specified index.
The insertObjects:atArrangedObjectIndexes: iterates through the array of objects passed as the first
parameter, inserting each object into the arranged collection at the corresponding index in the NSIndexSet.
Similarly, the insertObject:atArrangedObjectIndex: method inserts the single object specified as the
first parameter into the collection at the specified index.
NSTreeController provides four additional methods that operate in a similar fashion, but use NSIndexPaths to
specify the location in the collection rather than simple indexes:
-(void)removeObjectsAtArrangedObjectIndexPaths:(NSArray *)indexPaths;
-(void)removeObjectAtArrangedObjectIndexPath:(NSIndexPath *)indexPath;
-(void)insertObject:(id)object atArrangedObjectIndexPath:(NSIndexPath *)indexPath;
-(void)insertObjects:(NSArray *)objects atArrangedObjectIndexPaths:(NSArray
*)indexPath;
Note: NSArrayController and NSTreeController are optimized for insertions and deletions made with
these methods. Using these methods can provide a performance increase over modifying the model
objects directly and relying on key-value observing.
Modifying Controller Content by Target-Action
In addition to the programmatic add, insert, and remove methods, the controller classes implement several
target-action methods for modifying the controller’s content. These methods are typically configured as the
actions for buttons in Interface Builder.
Note: In OS X v10.4 the target-action methods are deferred so that the error mechanism can provide
feedback as a sheet.
NSObjectController provides the following target-action methods:
- (void)add:(id)sender;
Providing Controller Content
Modifying Controller Content by Target-Action
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
42- (void)remove:(id)sender;
The add: method creates a new content object using the controller’s newObject method and sets it as the
content object of the controller if there is currently no content. The remove: method sets the content object
to nil.
NSArrayController overrides NSObjectController’s add: and remove: methods and addsthe following method:
- (void)insert:(id)sender;
With an array controller add: creates a new object using the controller’s newObject method and appends it
to the controller’s content collection. The remove: method removesthe currently selected objectsin the array
controller from the collection. The insert: method creates a new object using the controller’s newObject
method, and inserts it after the current selection.
When the controller isin entity mode the remove semantics are dependent on the configuration of the binding
and on the entities definition of the relationship. A remove may result in the current selection being removed
from a relationship or being removed from the object graph entirely.
NSTreeController adds the following methods:
- (void)addChild:(id)sender;
- (void)insertChild:(id)sender;
The addChild: method creates and inserts a new object at the end of the tree controller’s collection. The
insertChild: method inserts a new child object relative to the current selection.
Again, the semantics are slightly different if the controller is in entity mode. The add: and insert: actions
use the newObject method to create the object that is added to the collection. In object mode the addChild:,
and insertChild: create objects of the class specified by objectClass, but do not use the newObject
method to do so. In entity mode or if the parent object is a managed object subclass, the entity defines the
class of object created for and newObject is never called.
In order to enable and disable the target-action buttons as appropriate, the object controller classes provide
several methodsthat return Boolean valuesindicating which actions are currently possible, taking into account
the controller’s current selection and configuration.
//NSObjectController
- (BOOL)canAdd;
Providing Controller Content
Modifying Controller Content by Target-Action
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
43- (BOOL)canRemove;
- (BOOL)isEditable;
//NSArrayController
- (BOOL)canInsert;
//NSTreeController
- (BOOL)canAddChild;
- (BOOL)canInsertChild;
The enabled binding of controls are typically bound to the controller using one of these methods. As the
controller’sselection changesthe valuesreturned by these methods are updated and the bound user interface
items are automatically enabled or disabled as appropriate.
De-coupling a Controller from its Content Bindings
In order to programmatically de-couple a controller from a content object that is bound to contentObject,
contentArray or contentSet, you must break the binding connection and set the controller’s content to
nil.
[theController unbind:@"contentArray"];
[theController setContent:nil];
Providing Controller Content
De-coupling a Controller from its Content Bindings
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
44NSObjectController and itssubclasses NSArrayController and NSTreeControllersupport tracking of the currently
selected object or objects. This article explains how to get a controller’s current selection, change the current
selection, and fine-tune the selection behaviors.
Getting a Controller’s Currently Selected Objects
There are two methods that are commonly used to access the objects that are currently selected: selection
and selectedObjects.
NSObjectController and itssubclassesimplement the selection method. This method returns a proxy object
that represents the receiver’s current selection. The proxy is fully key-value-coding compliant.
When you request a key’s value from the selection proxy it returnsthe value, or a selection marker. Placeholder
markers provide additional information about the selection. There are three placeholder markers defined in
the NSPlaceholders informal protocol:
● NSNoSelectionMarker
The NSNoSelectionMarker indicates that there are no items selected in the controller.
● NSNotApplicableMarker
The NSNotApplicableMarker indicates that the underlying object is not key-value-coding compliant for
the requested key.
● NSMultipleValuesMarker
The NSMultipleValuesMarker indicates that more than one object is selected in the controller and the
values for the requested key aren’t the same.
By default controllers return the NSMultipleValuesMarker only when the values for the requested key
differ. For example, if the value for selection.name returns an array containing three strings—”Tony”,
“Tony”, “Tony”—the string “Tony” is returned instead of the NSMultipleValuesMarker.
A collection controller can be configured—either programmatically using the method
setAlwaysUsesMultipleValuesMarker: or by checking the “Always uses multiple values marker”
checkbox in Interface Builder—such that it always returns NSMultipleValuesMarker when multiple items
are selected, even if the values are equal.
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
45
Working With a Controller’s SelectionSome bindings, such as NSTextField’s value binding, allow you to replace selection markers with custom
values that are treated as placeholder values by the controls. These replacement values are specified in the
Bindings inspector in Interface Builder. Bindings established programmatically can provide values for the
NSNoSelectionPlaceholderBindingOption, NSNotApplicablePlaceholderBindingOption,
NSNullPlaceholderBindingOption or NSRaisesForNotApplicableKeysBindingOption keys, defined
in the NSKeyValueBindingCreation informal protocol, in the options dictionary passed to
bind:toObject:withKeyPath:options:. The NSPlaceholders protocol also provides two class methods
setDefaultPlaceholder:forMarker:withBinding: and
defaultPlaceholderForMarker:withBinding: that allow you to provide default placeholders for the
specified selection markers for a binding.
Often you need to directly access the objects currently selected by the controller, rather than the proxy object
returned by selection. NSObjectController and its subclasses provide the selectedObjects method to
allow you to do just that. This method returns an array containing the objects that are currently selected by
the receiver. NSObjectController’s implementation returns an array containing a single object, the content
object.
Note: You can establish bindings to a controller’s selection method or the selectedObjects
method. However, you should avoid binding through the selectedObjects array, for example
selectedObjects.name. Instead, you should use selection.name. Similarly, you should avoid
observing keysthrough the array returned by selectedObjects. The proxy returned by selection
is more efficient at managing changes in key-value observing as the selection changes.
Changing the Current Selection
The collection controllers provide methods that allow you to modify the current selection by adding and
removing objects, or replacing the selection entirely.
All the methods that change a controller’s selection return a boolean value that indicates if the selection was
successfully changed. Thisis because an attempt to change the selection may cause a commitEditing message
to be sent which may fail or be denied, perhaps due to a value failing validation. If the selection is changed
successfully, these methods return YES, otherwise they return NO.
Changing the Selection by Object
NSArrayController providesthe following methodsfor replacing the controller’sselection completely or adding
and removing objects from the current selection:
Working With a Controller’s Selection
Changing the Current Selection
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
46- (BOOL)addSelectedObjects:(NSArray *)objects;
- (BOOL)removeSelectedObjects:(NSArray *)objects;
- (BOOL)setSelectedObjects:(NSArray *)objects;
All three methods require that you pass an array containing the objects as the parameter.
Getting and Setting Selection by Index
The collection controller classes provide additional methods to get the current selection as a set of indexes to
the objects in the collection.
NSArrayController provides the following methods for getting and setting the selection by index:
- (unsigned int)selectionIndex;
- (BOOL)setSelectionIndex:(unsigned int)index;
- (NSIndexSet *)selectionIndexes;
- (BOOL)setSelectionIndexes:(NSIndexSet *)indexes;
- (BOOL)addSelectionIndexes:(NSIndexSet *)indexes;
- (BOOL)removeSelectionIndexes:(NSIndexSet *)indexes;
The selectionIndexes method returns an NSIndexSet that specifies the indexes of all the selected objects.
The convenience method selectionIndex returns the index of the first selected object as an unsigned
integer.
You can explicitly set the selection of the controller using the setSelectionIndexes: method, passing an
NSIndexSet thatspecifiesthe indexes of the itemsthatshould become the selection. The setSelectionIndex:
method is a convenience method that lets you set the selection to a single index. If the selection is changed
successfully, these methods return YES.
The addSelectionIndexes: method attempts to add objects specified in the NSIndexSet parameter to the
current selection. Similarly, the removeSelectionIndexes: attempts to remove the objects specified by
index in the NSIndexSet parameter from the selection. If the selection is changed successfully, the methods
return YES.
NSTreeController treats data as an array of dictionaries of arrays,so a simple index of the selection isn’tsufficient.
Instead NSTreeController uses an NSIndexPath to specify the location of an object in the tree. An NSIndexPath
specifies the “path” to the selection as a series of indexes into the arrays, for example “1.4.2.3”.
NSTreeController provides the following methods for getting and setting the selection by index path:
Working With a Controller’s Selection
Changing the Current Selection
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
47-(NSIndexPath *)selectionIndexPath
-(NSArray *)selectionIndexPaths
-(BOOL)setSelectionIndexPath:(NSIndexPath *)indexPath
-(BOOL)setSelectionIndexPaths:(NSArray *)indexPaths
-(BOOL)addSelectionIndexPaths:(NSArray *)indexPaths
-(BOOL)removeSelectionIndexPaths:(NSArray *)indexPaths
The selectionIndexPaths: method returns an array of NSIndexPath objects that represent each of the
currently selected objects. The convenience method selectionIndexPath returns the first selected object
as an NSIndexPath.
You explicitly set the current selection using the setSelectionIndexPaths: method, passing an NSArray
of NSIndexPath objects as the parameter. The convenience method setSelectionIndexPath: sets the
selection to the single object specified by the NSIndexPath parameter.
The methods addSelectionIndexPaths: and removeSelectionIndexPaths: add or remove the objects
at the index paths specified in the array from the selection. As with the other selection modifying methods
they return a boolean value of YES if the selection was successfully changed.
Setting the Selection Behaviors
The collection controllers provide several methods that allow you to fine-tune how selection is maintained by
a controller, and how values are returned by the selection method.
Avoids Empty Selection
Often it is desirable to attempt to always have at least one item in a collection selected after an action such as
removing an item. This is the default behavior of the collection controllers.
You can modify this behavior using the method setAvoidsEmptySelection: passing NO as the parameter.
This allows the controller to have an empty selection, even if there are objects in the content. You can query
the current behavior using the method avoidsEmptySelection.
Interface Builder allows you to change this behavior for a collection controller by unchecking the “Avoids
empty selection” checkbox in the controller’s inspector panel.
Working With a Controller’s Selection
Setting the Selection Behaviors
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
48Selecting Objects Upon Insertion
By default a collection controller automatically selects objects asthey are inserted. While thisis often the correct
behavior, if you are inserting many objects into the collection it is inefficient and can degrade performance.
You can turn off this behavior using the setSelectsInsertedObjects: method. Passing YES as the
parameter causes all newly inserted objectsto be selected. If NO is passed asthe parameter, the currentselection
is unchanged as objects are inserted. The method selectsInsertedObjects returns a boolean indicating
if newly inserted objects will be selected.
You can also change this setting in Interface Builder by unchecking the “Selects inserted objects” checkbox in
the controller’s inspector panel.
Always Uses Multiple Values Marker
The NSMultipleValuesMarker indicates that more than one object is selected in the controller and the values
for the requested key aren’t the same. If the values are the same the value is returned rather than the selection
marker. While this allows editing of that common value across all the selected objects, it may not be the desired
result.
You can force all multiple selections to always return the NSMultipleValuesMarker using the method
setAlwaysUsesMultipleValuesMarker:, passing YES asthe parameter. You query the state of thissetting
using the method alwaysUsesMultipleValuesMarker.
This setting can also be changed in Interface Builder by checking the “Always use multiple values marker”
checkbox.
With large selections, enabling this option can improve performance.
Working With a Controller’s Selection
Setting the Selection Behaviors
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
49Preserves Selection
When the content of a collection controller changes, the default behavior isto attempt to find matching objects
for the current selection in the new content. Figure 1 shows a master-detail interface in which the selected
object in the detail NSTableView is automatically selected when the user selects a different activity in the
master NSTableView.
Figure 1 Preserves selection example
Daniel is selected in the “Chess Club” activity list. Changing the selection to “Swim Team” in the activity list,
the Daniel member is selected.
This behavior can be disabled by calling the setPreservesSelection: method, passing NO asthe parameter.
The current state is queried using the preservesSelection method which returns a boolean. You can also
change this setting in Interface Builder by unchecking the “Preserves selection” option in the controller’s
inspector.
While the default behavior is often appropriate, there can be performance implications. When the content
changes, the current selection must first be cached, and then the new content collection must be searched
for matching objects. This can become costly when dealing with large collections and multiple selections.
Selects All When Setting Content
There is a per-binding option that allows you further control over the selection when using an NSArrayController.
Working With a Controller’s Selection
Setting the Selection Behaviors
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
50The NSSelectsAllWhenSettingContentBindingOption causesthe array controller to automatically select
all the items in the array when the application changes the contentArray, contentSet, contentObject
or contentArrayForMultipleSelection value of the controller.
Figure 2 Selects all when setting content example
Initially Daniel is selected in the “Chess Club” member list. Changing the selection to “Swim Team” causes all
team members to be selected.
Figure 2 shows an application with a master-detail interface. The detail NSTableView displays a single member
selection “Daniel”. When the user selects the “Swim Team” item in the master NSTableView all the members
are selected automatically.
This option is set either in the Bindings inspector in Interface Builder, or by setting an NSNumber object with
a boolean value of YES as the NSSelectsAllWhenSettingContentBindingOption value in the options
dictionary passed to bind:toObject:withKeyPath:options:.
The “Selects All When Setting Content” option is also useful when creating inspectorsfor master-detail interfaces
that allow multiple selections to occur in the master interface. The selection that is to be inspected is
predetermined by the master array controller. The inspector array controller is bound to the master array
controller's selectedObjects binding,specifying the “Selects All When Setting Content” option. This ensures
that all items in the master controller's current selection are always selected in the inspector. In an
NSDocument-based application, the detail array controller's contentArray binding is typically bound to the
NSApplication instance using the mainWindow.windowController.document.
key path, substituting the master array controller's key for the final key in the key path.
Working With a Controller’s Selection
Setting the Selection Behaviors
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
51A binding has a number of possible options that can be configured, and many of these will influence the flow
of messages between the controller, view and model objects. Value transformers, NSFormatters, key-value
validation, placeholders, selection value markers, and other binding options, all affect changes made in the
user interface, and in the model values. This article describes the flow of messages between the objects for
common interactions with the view, controller, and model objects.
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
52
Bindings Message FlowChanging the Value of a Model Property
The diagram in Figure 1 showsthe flow of messages between the model, controller and view objectsin response
to changing the value of a property in the model object.
Figure 1 Message flow when modifying the value of a model object property
View
Controller
Model
Yes
Yes
No
No
No
Yes
Is the value a selection marker?
9
NSTextField contents are updated using setObjectValue:.
11
NSTextField applies its NSFormatter, if specified.
12
Updated value is displayed in the NSTextField.
17
Objects observing the binding property of the
controller are notified of the value change.
6
Controller generates a key-value observing message,
mapping the model key-path
to the appropriate controller key-path.
5
Objects observing binding properties that have
changed as a result of the new content are notified.
14
Does the binding specify
a placeholder for the selection marker?
15
NSTextField's placeholder is set with the
view-controller's placeholder value.
16
Content object is transformed using the
controller-model value transformer, if specified.
13
Objects observing the property of the model object
are notified that the value has changed.
2
Value of a property is modified
using a key-value observing compliant method.
1
Controller receives the notification
that the value has changed.
3
Does the controller-model binding
have "Handle as Compound Value" enabled?
4
NSTextField gets the current value from the
controller using key-value coding.
8
Value is transformed using the view-controller
binding value transformer, if specified.
10
NSTextField receives notification of the value change.
7
Bindings Message Flow
Changing the Value of a Model Property
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
53Specifically, this example is of an NSTextField with its value binding bound to an NSObjectController’sselection
using the selection.firstName key path. The NSObjectController’s content binding is bound to a model
object that encapsulates a person’s name and address.
1. A property of the model object is changed using a key-value-observing compliant means.
2. The objects that are registered with the model object to receive key-value observing change notifications
are sent an observeValueForKeyPath:ofObject:change:context: message by the model object’s
inherited key-value observing implementation.
3. The controller receives the observeValueForKeyPath:ofObject:change:context: message. The
controller is registered with the model object as a result establishing the binding to the model object.
4. The controller tests to see if the controller-model binding has the “Handles as Compound Value” option
set. If “Handles as Compound Value” is enabled, proceed to Step 13.
5. The controller generates a key-value observing message for the value that has changed, mapping the
model key-path to the appropriate controller key-path. For example, if the key path of the property in the
model objectis“firstName”then the key-value observing change ismapped to “selection.firstName”.
6. The objectsthat are registered with the controller object to receive key-value observing change notifications
are sent an observeValueForKeyPath:ofObject:change:context: message by the controller
object’s inherited key-value observing implementation.
7. The view receives the observeValueForKeyPath:ofObject:change:context: message. The view
is registered with the controller object as a result establishing the binding to the controller object.
8. The view objects gets the current value for the changed property from the controller using key-value
coding.
9. The view tests the value retrieved in Step 8. If the value is one of the selection markers, or nil, proceed
to Step 15.
10. If the view-controller binding specifies a value transformer, the value retrieved in Step 8 is passed to the
value transformer’s transformedValue: method. The value returned by the transformedValue:
method is passed to the next step.
11. The view setsits contentsto the updated, possibly transformed, value using the view’s setObjectValue:
method.
12. If the view has an NSFormatter attached to it, the value is formatted by the NSFormatter instance. Proceed
to Step 17.
13. If the controller-model binding specifies a value transformer the entire model is transformed using the
transformedValue: method.
14. The controller generates a key-value observing message, mapping the key path to the selection. This
generates the same type of key-value observing change notification that would result in replacing the
content object. Proceed to Step 7.
Bindings Message Flow
Changing the Value of a Model Property
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
5415. If the binding specifies a placeholder for the view-controller binding for the value marker it is used in Step
16. If no custom placeholder value is specified as part of the binding, and a custom default placeholder
has been set for the view class and this particular binding, it is used in Step 16. Otherwise, flow returns to
Step 11.
An application can assign a custom default placeholder for a class and binding combination using the
NSPlaceholders class method setDefaultPlaceholder:forMarker:withBinding:.
16. The placeholder value is set for the view using the setPlaceholderString: or
setPlaceholderAttributedString: method.
17. The updated value is displayed in the user interface.
Bindings Message Flow
Changing the Value of a Model Property
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
55User Updates a Value in the User Interface
The diagrams in Figure 2 and Figure 3 (page 58) show the flow of messages between the model, controller
and view objects in response to changing the value of a property in the model object.
Bindings Message Flow
User Updates a Value in the User Interface
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
56Figure 2 Message flow in response to the user changing a value in an NSTextField
View
Controller
Model
Yes
Yes
User enters new value into the NSTextField.
1
Is "Handle as Compound Value"
enabled for the controller-model binding?
6
Is "Validates immediately" enabled
for the controller-model binding?
19
Invokes setValue:forKeyPath
on the model with the
new content object.
22
User is notified
that value is invalid.
21
Invokes setValue:forKeyPath:
on the new content object with the new property value.
17
New content object is transformed using the
controller-model binding value transformer, if specified.
16
Gets the current content object from the model
using valueForKeyPath:.
15
Invokes setValue:forKeyPath on the model
passing the object value and the bound key path.
7
Invokes setValue:forKeyPath on the controller
passing the object value and the binding's key path.
5
NSTextField attempts to validate the value
using the NSFormatter, if specified.
2
Is "Validates immediately"
enabled for the view-controller binding?
4
Value is transformed using the
view-controller binding value transformer, if specified
and if it supports reverse transformation.
3
Content object is transformed using the
controller-model binding value transformer, if specified
and if it supports reverse transformation.
18
Attempts to validate the new content object
using key-value validation by invoking
validateValue:forKeyPath:error: on the model.
20
Updated value now stored in model object.
Key-value observing notifications
are sent to observers of this model property.
23
Yes
Valid
No
No
No
Valid or Invalid
coerced value
8
See Figure 3.
13
See Figure 3.
Bindings Message Flow
User Updates a Value in the User Interface
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
57Figure 3 Message flow in response to the user changing a value in an NSTextField, view-controller validation
View
Controller
Model
Attempts to validate the value using key-value validation
by invoking validate:error:
on self.
11 10
Returns original valid value
coerced value,
or invalid
Returns original valid value
coerced value,
or invalid
Attempts to validate the value using key-value validation
by invoking validateValue:forKeyPath:error:
on the controller.
8
Attempts to validate the value using key-value validation
by invoking validateValue:forKeyPath:error:
on the model.
12 9
Valid or
coerced value
Invalid
4 Yes
See Figure 2.
5
See Figure 2.
User is notified
that value is invalid.
14
Is the value valid,
was coerced valid, or invalid?
13
As in “"Changing the Value of a Model Property" (page 53)” this specific example is of an NSTextField with its
value binding bound to an NSObjectController’s selection using the selection.firstName key path. The
NSObjectController’s content binding is bound to a model object that encapsulates a person’s name and
address.
1. The user enters a new value into the NSTextField.
2. If an NSFormatter is attached to the NSTextField the formatter attemptsto validate the value. If the formatter
fails to validate the value it provides failure feedback and returns control to the user.
The view-controller binding option “Continuously Updates Value” determines when the view notifies the
controller of changes in the text field. If it this option is specified, the controller will be updated for each
keystroke. If it is disabled then the controller is only updated when the user hits return, tab, or the text
field loses first responder status.
3. If the view-controller binding specifies a value transformer and the value transformer supports inverse
transformations, the new value is transformed using the inverseTransformedValue: method. The
value returned by the inverseTransformedValue: method is passed to the next step.
4. If the view-controller binding option “Validates Immediately” is enabled, then proceed to Step 8.
Bindings Message Flow
User Updates a Value in the User Interface
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
585. The view object invokes setValue:forKeyPath: on the controller object, passing the new, possibly
transformed, object value and the binding’s key path as arguments.
6. If the controller-model binding option “Handle as Compound Value” is enabled, then proceed to Step 15.
7. The controller invokes setValue:forKeyPath: on the model object, passing the new content value
from Step 5 and the key path for the property in the model object as arguments. Proceed to Step 23.
8. The view attempts to validate the value from Step 4 using key-value validation. It does this by invoking
validateValue:forKeyPath:error: on the controller, passing the proposed value, the binding’s key
path, and an NSError pointer reference as arguments.
9. The controller receivesthe validateValue:forKeyPath:error: message and forwardsthe validation
request to the model object’s validateValue:forKeyPath:error: implementation, modifying the
key path so that it references the appropriate model object property.
10. The model object receivesthe validateValue:forKeyPath:error: message and attemptsto validate
the proposed value by invoking validate:error: on self. The specific method signature will be
dependent on the name of the property.
The model object’simplementation of validate:error: returns a Boolean value indicating if the
value was valid. If the result is YES, then the proposed value was valid, or was replaced with a validated
value. If the result is NO, then the proposed value is invalid and the error reference should contain a
description of why the validation failed.
11. The validation result, value, and a possible error are returned by the model’s implementation of
validate:error: to the controller’s validateValue:forKeyPath:error: method.
12. The validation result, value, and a possible error are returned by the controller’s
validateValue:forKeyPath:error: method to the view.
13. If the validation result is YES, then the valid or coerced value are returned to Step 5, otherwise proceed
to Step 14.
14. The user is notified that the value is invalid.
How the error is presented to the user is dependent on the view-controller binding’s “Always Presents
Application Modal Alert Panels” option. If this option is enabled, then the error is displayed to the user as
a modal alert panel. If it is not enabled, then the error is presented to the user as a sheet.
15. The controller gets the current content object using valueForKeyPath: on the model object.
This is the message flow path for “Handle as Compound Value”. The original content object specified by
the contentObject, contentArray or contentSet binding is retrieved from the model, transformed,
the new value from the user inserted, the content object isinverse transformed, and set asthe new content
object.
16. If the controller-model binding specifies a value transformer the content object retrieved in Step 15 is
transformed using the transformedValue: method. The value returned by the transformedValue:
method is passed to the next step.
Bindings Message Flow
User Updates a Value in the User Interface
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
5917. The controller invokes setValue:forKeyPath: on the retrieved, possibly transformed, content object.
The value from Step 5 and the key path for the property in the model object as arguments.
18. If the controller-model binding specifies a value transformer and the value transformer supports inverse
transformations, the content object from Step 17 istransformed using the inverseTransformedValue:
method. The value returned by the inverseTransformedValue: method is passed to the next step.
19. If the controller-model binding option “Validates Immediately” is enabled, proceed to Step 20, otherwise
proceed to Step 22.
20. The controller attempts to validate the new, possibly transformed, content object by invoking
validateValue:forKeyPath:error: on the model object.
If validateValue:forKeyPath:error: returns YES, then the valid or coerced valid value is passed to
Step 22. If the result is invalid, proceed to Step 21.
21. The user is notified that the value is invalid.
How the error is presented to the user is dependent on the controller-controller binding’s“Always Presents
Application Modal Alert Panels” option. If this option is enabled, then the error is displayed to the user as
a modal alert panel. If it is not enabled, then the error is presented to the user as a sheet.
22. The controller invokes setValue:forKeyPath: on the model object using the new, possibly validated
content object and the content key path as arguments.
23. The updated value is now stored in the model object.
Changing the model object resultsin key-value observing change notifications being sent to the observers
of the model property.
Bindings Message Flow
User Updates a Value in the User Interface
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
60Many applications provide a preferences window that allows the user to customize an application’s settings.
NSUserDefaultsController provides a layer on top of NSUserDefaults and allows you to bind attributes of user
interface items to the corresponding key in an application’s user defaults.
What Is NSUserDefaultsController?
NSUserDefaultsController is a concrete subclass of NSController that implements a bindings-compatible interface
to NSUserDefaults. Properties of an instance of NSUserDefaultsController are bound to user interface items to
access and modify values stored using NSUserDefaults.
NSUserDefaultsController istypically used when implementing your application’s preference window interface,
or when you can bind a user interface item directly to a default value. NSUserDefaults remains the primary
programmatic interface to your application’s default values for the rest of your application.
By default NSUserDefaultsController immediately applies any changes made to its properties. It can be configured
so that changes are not applied until it receives an applyChanges: message, allowing the preferences dialog
to support an Apply button. NSUserDefaultsController also supports reverting to the last applied set of values,
using the revert: method.
NSUserDefaultsController also allows you to provide a dictionary of factory defaults that can be used to reset
the user configurable valuesfor your application, usually done in response to a user clicking a Revert to Factory
Defaults button.
The Shared User Defaults Controller
NSUserDefaultsController provides a shared instance of itself via the class method
sharedUserDefaultsController. This shared instance uses the NSUserDefaults instance returned by the
method standardUserDefaults as its model, has no initial values, and immediately applies changes made
through its bindings.
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
61
User Defaults and BindingsCare must be taken that changes to the settings of the shared user defaults controller are made before any
nib files containing bindingsto the shared controller are loaded. To ensure that these changes are made before
any nib files are loaded, they are often implemented in the initialize class method of the application
delegate, or in your preferences window controller.
Binding to the Shared User Defaults Controller
The shared NSUserDefaultsController is always available as a bindable controller in the Bindings Info window
in Interface Builder. When establishing a binding to a user default, set the Controller Key to values, and the
Model Key Path to the key of the default.
Creating bindings programmatically requires that you retrieve the shared user defaults controller using the
NSUserDefaultsController class method sharedUserDefaultsController. You then provide that object as
the observableController to the bind:toObject:withKeyPath:options: method.
The example in Listing 1 establishes a binding between an NSTextField (theTextField) and the userName
default using the shared user defaults controller.
Listing 1 Binding the userName defaults key to an NSTextField programmatically
[theTextField bind:@"value"
toObject:[NSUserDefaultsController sharedUserDefaultsController]
withKeyPath:@"values.userName"
options:[NSDictionary dictionaryWithObject:[NSNumber
numberWithBool:YES]
forKey:@"NSContinuouslyUpdatesValue"]];
initialValues Versus NSUserDefaults registerDefaults:
The initial values dictionary allows you to provide a means to reset the user configurable default values to the
factory defaults. Typically these values represent a subset of the defaults that your application registers using
the NSUserDefaults method registerDefaults:.
Calling the NSUserDefaultsController method setInitialValues: should not be considered a replacement
for registering your application's preference defaults using NSUserDefault's registerDefaults: method.
User Defaults and Bindings
Binding to the Shared User Defaults Controller
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
62The example in Listing 2 loads the default values from a file in the application wrapper, registers those values
with NSUserDefaults, and then registers a subset of the values as the initial values of the shared user defaults
controller. The setupDefaults method would be called from your application delegate’s initialize class
method.
Listing 2 Changing the initial values of the sharedUserDefaultsController instance
+ (void)setupDefaults
{
NSString *userDefaultsValuesPath;
NSDictionary *userDefaultsValuesDict;
NSDictionary *initialValuesDict;
NSArray *resettableUserDefaultsKeys;
// load the default values for the user defaults
userDefaultsValuesPath=[[NSBundle mainBundle] pathForResource:@"UserDefaults"
ofType:@"plist"];
userDefaultsValuesDict=[NSDictionary
dictionaryWithContentsOfFile:userDefaultsValuesPath];
// set them in the standard user defaults
[[NSUserDefaults standardUserDefaults] registerDefaults:userDefaultsValuesDict];
// if your application supports resetting a subset of the defaults to
// factory values, you should set those values
// in the shared user defaults controller
resettableUserDefaultsKeys=[NSArray
arrayWithObjects:@"Value1",@"Value2",@"Value3",nil];
initialValuesDict=[userDefaultsValuesDict
dictionaryWithValuesForKeys:resettableUserDefaultsKeys];
// Set the initial values in the shared user defaults controller
[[NSUserDefaultsController sharedUserDefaultsController]
setInitialValues:initialValuesDict];
}
User Defaults and Bindings
initialValues Versus NSUserDefaults registerDefaults:
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
63Search Order for Defaults Values
When a method that is key-value coding compliant attempts to get a value for a key from an
NSUserDefaultsController the following search pattern is used:
1. The value of a corresponding key in values
2. The value of a corresponding key in the NSUserDefaultsinstance returned by the NSUserDefaultsController
method defaults.
3. The value of a corresponding key in the initial values dictionary
If no corresponding value is found, nil is returned.
The search path is somewhat different when you retrieve the result directly from the NSUserDefaults instance
associated with the NSUserDefaultsController. In that case, any unapplied valuesin the NSUserDefaultsController,
as well as the values in the initial values dictionary are ignored.
Programmatically Accessing NSUserDefaultsController Values
Although NSUserDefaults should remain your primary programmatic interface to the user defaults, some
circumstancesrequire that you get and set the default values contained in an NSUserDefaultsController instance
directly. For example, when implementing portions of your preferences window that don’t directly interact
with an existing binding, such as setting a font or choosing a directory path.
The NSUserDefaultsController method values returns a KVC-compliant object that is used to access these
default values. To get the value of a default, use the valueForKey: method.
[[theDefaultsController values] valueForKey:@"userName"];
Similarly, to set a value for a default, use setValue:forKey:.
[[theDefaultsController values] setValue:newUserName
forKey:@"userName"];
The NSUserDefaultsController automatically provides notification of the value change to any established
bindings for that key path.
User Defaults and Bindings
Search Order for Defaults Values
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
64This article explains how to use bindings to implement a basic master-detail interface. In the master interface
portion, a table view is used to display your collection of objects. In the detail interface portion, other views
are used to display the selected object. This article contains examples of binding table columns to array
controllers.
A master-detail interface can be extended in a variety of ways that are not fully covered in this article. In
particular, there are many aspects of a table view that work seamlessly with Cocoa bindings. Refer to these
other articles for specific tasks:
●
"Displaying Images Using Bindings" (page 73) describes the various options used when displaying images
in columns and contains an example of a custom value transformer.
●
"Implementing To-One Relationships Using Pop-Up Menus" (page 78) explains how to implement editable
to-one relationship as pop-up menus.
●
"Filtering Using a Custom Array Controller" (page 81) explains how to add a search field to the master
interface in order to filter the objects it displays.
What’s a Master-Detail Interface?
In a master-detail interface, the user can select objects from a list of objects and inspect the selected objects.
The master interface displays the collection of objects, and the detail interface implements an inspector of
the selected object. Whenever the user changes the selection in the master interface, the detail interface is
updated to show the new selection. If no object is selected, the detail interface displays nothing or disables
itself (if it is editable). If multiple objects are selected (assuming multiple selection is allowed), the detail interface
is disabled or it applies to all selected objects. Typically, such an interface also allows users to add and remove
objects from the collection.
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
65
Creating a Master-Detail InterfaceWhat components you use to implement a master-detail interface depend on your application. Often, the
master interface is implemented by a table view and the detail interface is a collection of views located above
or below the master interface. Figure 1 shows a master-detail interface in a media asset application. In this
example, the table view displays a collection of media objects and the views below display the properties of
the selected media object.
Figure 1 A master-detail interface
Master interface
Detail interface
Creating Models
This article assumes that you have already designed your model classes, the objects that encapsulate your
application data and provide methods to operate on that data. A master-detail interface is used to display a
collection of model objects. Therefore, your root model object is expected to be a collection of these objects.
Creating a Master-Detail Interface
Creating Models
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
66For example, Figure 2 illustratesthe object diagram of the modelsin a media asset application. In this example,
assume the application uses a document-based architecture in which MyDocument (the File’s Owner of the
MyDocument.nib file) has a mediaAssets instance variable that is initialized to an array of Media objects.
Figure 2 An object model
anImage
...
aMedia
title
date
image
author
anAddress
street
city
zip
county
aPhone
work
home
aPerson mobile
firstName
lastName
phone
address
Creating Views
The master interface shown in Figure 1 (page 66) uses a table view in which each column in the table
corresponds to a property of the displayed objects. Note that the table view displays attributes such as the
title and date, as well as relationships such as the author. The author property is a to-one relationship (see
Figure 2 (page 67)) in which the destination object is a Person object. You can display to-one relationships in
a variety of ways. For example, the master interface displays only the author’s last name. The detail interface
lets you select the author from a pop-up menu.
Using Interface Builder, lay out your master-detail interface similar to Figure 1 (page 66). In this example, an
image cell was dragged to the Image column to display a scaled version of the media image and a date formatter
was dragged to the Date column. The Author column displays the last name of the authors but could be
configured to display a menu as well by dragging a pop-up menu cell, NSPopUpButtonCell, to the Author
column.
Creating a Master-Detail Interface
Creating Views
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
67Creating Controllers
Next you need to connect your viewsto your models via a controller. NSArrayController isspecifically designed
to manage selectable collections of objects. For thisreason, it isideal for implementing a master-detail interface.
After you create an array controller, you specify its content to be your collection of model objects. If the array
controller is editable, it can also create instances of models and add them to the collection. For this to work,
you also need to tell the array controller the class name the entity it is managing.
Follow these steps to create and configure an array controller for a master-detail interface:
1. Create an array controller by dragging an NSArrayController from the Interface Builder Controllers palette
to your nib file, and optionally change the name of the controller. For example, change the name to
MediaAssetsController.
2. Enter the class name of your model in the Object Class Name field on the Attributes pane of the Info
window as shown in Figure 3. In the media asset application example, you would enter Media as the class
name.
3. Deselect Editable on the Attributes pane if you don’t want the user to edit the models, or add and remove
them from the collection.
Figure 3 An array controller Attributes pane
Creating a Master-Detail Interface
Creating Controllers
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
68Binding Controllers to Models
Each view and controller class exposes a set of bindings. You can inspect the bindings of an object using
Interface Builder by selecting it and displaying the Bindings pane in the Info window. Select your array controller
in the nib file and display its bindings as shown in Figure 4 (page 69).
You specify the content of an array controller by configuring its contentArray binding. For example, assuming
the File’s Owner maintains a collection of Media objects called mediaAssets, you would configure the
contentArray binding as follows:
1. Set the Bind to aspect to the object that maintains the collection of model objects (for example, the
File’s Owner).
2. Leave the Controller Key blank.
3. Set the Model Key Path to the name of the array (for example, mediaAssets).
Figure 4 shows what the Bindings pane should look like when the contentArray binding is revealed.
Figure 4 An array controller Bindings pane
Now this controller is configured to manage a collection of Media objects including adding and removing
Media objects from the array. Go back to the Attributes pane if you want to change the default behavior of
this controller—deselect Editable if you don’t want the user to edit the collection.
Creating a Master-Detail Interface
Binding Controllers to Models
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
69Binding Views to Controllers
Next, connect your views to your array controller by configuring the bindings of your view objects—bind each
view in the master and detail interfaces to the array controller.
Setting some of these bindings can be complex if, for example, you want to represent an editable to-one
relationship. This article covers the most common types of value bindings. See "Displaying Images Using
Bindings" (page 73) and "Implementing To-One Relationships Using Pop-Up Menus" (page 78) for examples
of more complex bindings.
In general, it helps to remember that most views that display some content have a binding called value or a
binding that contains the word “value.”
Also, because you bind a view to a controller, you need to be familiar with the properties of controllers. These
are the primary properties of an array controller that you typically use as the value of the Controller Key
aspect:
● arrangedObjects—specifies the collection of objects being displayed.
● selection—specifies the selected object in arrangedObjects.
● selectedObjects—specifies the selected objects in arrangedObjects for a multiple selection.
Binding the Master Interface
In the master interface, you actually configure the bindings of the table columns, not the bindings of a table
view. Even if you use a special type of cell in the table column, for example, an image cell or NSPopUpButtonCell,
you configure the table column, not the cell. The bindings for an NSTableColumn may change depending on
its cell.
In this example, each row in the table view needs to correspond to a single Media object, and each column
needs to display a property of that Media object. To display properties, you first select a table column in the
table view, display the Bindings pane, and reveal the value binding. You will then bind the table column to
the controller’s arrangedObjects key because you are specifying the contents of the entire column, not a
single cell. Configure each value binding to display a property of the Media object as follows:
1. Set the Bind to aspect to your array controller object. For example, MediaAssetsController.
2. Set the Controller Key to arrangedObjects (the collection of objects being displayed).
3. Set the Model Key Path to the property you want to appear in that column. For example, in the media
asset application, set the Model Key Path to date in the Date column.
Creating a Master-Detail Interface
Binding Views to Controllers
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
70Figure 5 shows what the Bindings pane should look like when the value binding of the Title column isrevealed.
Figure 5 NSTableColumn Bindings pane
In the case of the author property, you can set the Model Key Path to author.lastname if you want to
simply display the last name. However, by default column cells are editable, so when you run your application,
users will be able to change the author’s last name by editing the text in the Author column. If you don’t want
this behavior, select the column and deselect Editable on the Attributes pane in the Info window.
Note that some Cocoa views already display lists of objects that work well with key-value coding. The
Controller Key and Model Key Path aspects of a binding are concatenated to retrieve the value from
the bound object. In the case of a table column, the Controller Key identifies an array; therefore the returned
value is an array. For example, the Author table column uses the arrangedObjects.author.lastname key
path to get an array of last names, instances of NSString, from the bound object.
Binding the Detail Interface
You bind the views in the detail interface similarly to the way you bind the master interface except that you
set the Controller Key for each binding to selection, the currently selected object.
For example, you configure the value binding for the Title text field in the detail interface depicted in Figure
1 (page 66) as follows:
1. Set the Bind to aspect to your array controller object. For example, MediaAssetsController.
Creating a Master-Detail Interface
Binding Views to Controllers
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
712. Set the Controller Key to selection (the currently selected object).
3. Set the Model Key Path to the property you wish displayed in that view. For example, title.
Creating a Master-Detail Interface
Binding Views to Controllers
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
72You typically display images using either an NSImageCell or an NSImageView. You might display images in
table columns in a master interface, or an image view in a detail interface. Both of these components have
similar bindings and therefore are both discussed in this article. When using these components, you also need
to make similar decisions about how you want to store and access your images. The value bindings of these
components support a variety of formats. You can specify the value binding using an NSImage, a file path or
a URL.
The example presented in this article is an extension of those in "Creating a Master-Detail Interface" (page 65).
Specifically, this article explains how to add an NSImageCell to a table column in the master interface (shown
in Figure 1) and an NSImageView to the detail interface. This article also contains an example of a custom value
transformer that converts image filenames to absolute paths.
See "Creating a Master-Detail Interface" (page 65) for the steps to create a basic master-detail interface.
Figure 1 Using an NSImageCell in an NSTableColumn
Creating Models
How you define image properties in your models depends on your application—it depends on how you want
to store and accessthe images. If the images are stored on disk or in your project folder on the same computer,
then you can access them via a file path. If the images are stored not on disk but on a remote server, then you
can access them via a URL. If the images are stored in some data source, then you might load the images
directly into memory. If you load them, you might define your image properties as simply NSImage objects.
Fortunately, Cocoa bindings supports all these options.
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
73
Displaying Images Using BindingsIn this example, the model uses an NSImage to represent the image property of a Media object as shown in
Figure 2 (page 67) in "Creating a Master-Detail Interface" (page 65).
In the case of a file path or URL, you typically store only the filename in the model not the absolute path or
URL (for example, you don’t want to update your models every time you move the image folder). However,
the bindings expect an absolute path or URL. One solution is to implement a custom value transformer that
takes an image filename and returns a file path or URL based on some variables you define. See "Using a Value
Transformer to Convert Paths" (page 75) to modify this example to use file paths instead of NSImage objects.
Creating Views
Next, you create the image views by either dragging an NSImageView to a window or an NSImageCell to the
column that will display the images.
Creating Controllers
This example assumes you already have a working master-detail interface. Follow the steps in "Creating
Controllers" (page 68) in “Creating a Master-Detail Interface” if you need to create an array controller.
Binding Views to Controllers
Next, you bind the image views to the controllers. A subset of the value bindings of an NSImageView and an
NSTableColumn (containing an NSImageCell) to choose from are:
● value—an NSImage object.
● valuePath—an absolute path to the image file.
● valueURL—a URL that returns the image file.
Note: When using an NSImageCell in a table column, you need to configure the bindings of the
table column, not the cell. When you drag an NSImageCell to a column, the Value category on the
NSTableColumn Bindings pane changes to reveal additional value bindings (displays the same value
bindings as an NSImageView).
For example, you configure the value binding for the Image table column in Figure 1 (page 73) as follows:
1. Set Bind to to your array controller object. For example, MediaAssetsController.
Displaying Images Using Bindings
Creating Views
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
742. Set Controller Key to arrangedObjects (the collection of objects being displayed).
3. Set Model Key Path to the NSImage property to display in that column. For example, in the media asset
application, set the key path to image.
You configure the value binding for the NSImageView in the detail interface similarly, except that you set the
Controller Key value to selection (that is, the currently selected object).
See "Using a Value Transformer to Convert Paths" (page 75) for a variation of this example that uses the
valuePath binding.
Using a Value Transformer to Convert Paths
If you want to access your images using a file path, then you bind your views to your controllers using the
valuePath binding instead. However, valuePath is expected to be an absolute file path. Typically, you do
not store absolute paths in your models, just filenames or relative paths. You can convert a filename or relative
path to an absolute path using a custom value transformer as follows.
Creating Custom Value Transformers
First create a custom value transformer that takes the filename or relative path and converts it to an absolute
path. You do this by subclassing NSValueTransformer and overriding the transformedValueClass and
allowsReverseTransformation class methods, as shown in Listing 1 (page 75).
You implement the transformedValue: method to perform the transformation. For example, the
transformedValue: method implementation in Listing 1 assumes the images are located in the project
Resources folder and uses NSBundle’s resourcePath: method to convert the filename to an absolute path.
Note that you need to modify this example if you store the images somewhere else on the file system.
Listing 1 PathTransformer implementation file
#import "PathTransformer.h"
@implementation PathTransformer
+ (Class)transformedValueClass
{
return [NSString self];
}
Displaying Images Using Bindings
Using a Value Transformer to Convert Paths
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
75+ (BOOL)allowsReverseTransformation
{
return NO;
}
- (id)transformedValue:(id)beforeObject
{
if (beforeObject == nil) return nil;
id resourcePath = [[NSBundle mainBundle] resourcePath];
return [resourcePath stringByAppendingPathComponent:beforeObject];
}
@end
Registering Value Transformers
In order to use your custom value transformer, you must first register it with NSValueTransformer. Note that
you register instances of a value transformer, not a subclass. You typically register value transformers in an
initialize method or the application delegate’s applicationDidFinishLaunching: method. When
you register a value transformer, you give it a logical name you can use later when configuring a bindings. For
example, add the following code fragment to applicationDidFinishLaunching: to register an instance
called PathTransformer:
id transformer = [[[PathTransformer alloc] init] autorelease];
[NSValueTransformer setValueTransformer:transformer forName:@"PathTransformer"];
Binding Views to Controllers Using Transformers
Finally, you specify the value transformer when binding your views to your controllers using the valuePath
binding. For example, you configure the valuePath binding for the Image table column as follows:
1. Set the Bind to aspect to your array controller object—for example, MediaAssetsController.
2. Set the Controller Key aspect to arrangedObjects (the collection of objects being displayed).
3. Set the Model Key Path aspect to the property containing the image filename. For example, in the
media asset application, set the key path to imagePath.
Displaying Images Using Bindings
Using a Value Transformer to Convert Paths
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
764. Enter the transformer’s logical name, PathTransformer , in the Value Transformer text field.
Figure 2 shows the Bindings pane of an NSTableColumn in Interface Builder with the valuePath binding
revealed and configured to use a custom value transformer.
Figure 2 Bindings pane of an NSTableColumn
To use the valueURL binding instead, implement a similar value transformer to convert filenames or relative
paths to an appropriate NSURL object. Optionally, you can enhance the PathTransformer class to handle other
types of filename transformations. In the latter case, register different instances using different names (for
example, PathTransformer and URLTransformer) to handle each type of transformation.
Displaying Images Using Bindings
Using a Value Transformer to Convert Paths
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
77You can implement an editable to-one relationship using an NSPopUpButtonCell or NSPopUpButton. That is,
allow the user to select a menu item from a pop-up menu in order to change the destination object of a to-one
relationship in your model.
This article extendsthe example presented in "Creating a Master-Detail Interface" (page 65) by adding a pop-up
menu to the table view in the master interface. Instead of displaying the author’s last name in the Author
column, you might allow users to select an author from a pop-up menu, as shown in Figure 1. Note that if you
display just the last name, you have essentially flattened the to-one relationship. Consequently, if the user
enters text in an editable Author column text cell, the user changes the value of the last name property
belonging to the Person object, not the destination of the to-one relationship belonging to the Media object.
Using pop-up menus is one way to implement an editable to-one relationship.
See "Creating a Master-Detail Interface" (page 65) for the steps to create a master-detail interface.
Figure 1 Using pop-up menus to represent to-one relationships
Creating Models and Controllers
To do this, you first need the supporting model and controller objects. Assuming you already have a master
interface, you create an array of models that will be displayed in the pop-up menu, and you create an array
controller to manage it.
How you initialize the collection of objectsto display in the pop-up depends on your application. This example
assumes that a collection already exists and is a property of the File’s Owner (for example, a property called
authors containing Person objects).
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
78
Implementing To-One Relationships Using Pop-Up
MenusCreate an NSArrayController by dragging it from the Cocoa-Controllers palette to your nib file, and rename it
appropriately (for example, AuthorsController). It’s common to have multiple controller objects per nib
file so renaming them helps to identify them. Now, bind the model to the controller as follows. Select the array
controller, display the Bindings pane in the Info window, reveal the contentArray binding, and configure it
as follows:
1. Set the Bind to aspect to the object that maintains the collection of model objects (for example, the
File’s Owner).
2. Leave the Controller Key blank.
3. Set the Model Key Path to the name of the array (for example, authors).
Also enter the appropriate class name in the Object Class Name field on the Attributes pane (for example, enter
the Person class name).
Creating Views
Next, you create the pop-up views by dragging an NSPopUpButton to a window or an NSPopUpButtonCell to
the column that will display the to-one relationship.
Binding Views to Controllers
The primary bindings of an NSPopUpMenu and NSTableColumn (containing an NSPopUpButtonCell) that you
will use to set up an editable to-one relationship are:
● content—the collection of objects to display in the pop-up menu.
● contentValues—the property of the objects in content that you want to display in the pop-up menu.
● selectedObject—the to-one relationship that users change when selecting an item from the pop-up
menu.
Note: When using an NSPopUpButtonCell, you need to configure the bindings of the table column,
not the cell. When you drag an NSPopUpButtonCell to a column, the Value category on the
NSTableColumn Bindings pane changes to a Value Selection category containing the same bindings
as an NSPopUpButton.
For example, if you want to display a pop-up menu in a column, configure the content binding to specify
the content of the pop-up menu as follows:
Implementing To-One Relationships Using Pop-Up Menus
Creating Views
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
791. Set the Bind to aspect to AuthorsController.
2. Set the Controller Key aspect to arrangedObjects .
3. Leave the Model Key Path aspect blank.
Then configure the contentValues binding to specify whatshould be displayed in the menu items asfollows:
1. Set the Bind to aspect to AuthorsController.
2. Set the Controller Key aspect to arrangedObjects.
3. Set the Model Key Path aspect to lastName.
Finally, configure the selectedObject binding to specify the actual to-one relationship this pop-up menu
changes as follows:
1. Set the Bind to aspect to MediaAssetsController.
2. Set the Controller Key aspect to arrangedObjects.
3. Set the Model Key Path aspect to author (a to-one relationship).
Now, when you run your application, a pop-up menu appears in each of the table column cells, displaying the
current value of the to-one relationship. When the user selects another item from the menu, the controller
changes the destination object of that to-one relationship.
Follow the same steps above to configure an NSPopUpButton as an editable to-one relationship. If you are
implementing a master-detail interface and this pop-up button appears in the detail interface, then set the
Controller Key for the selectedObject binding to selection, that is, to the currently selected object.
Implementing To-One Relationships Using Pop-Up Menus
Binding Views to Controllers
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
80This article explains how to use a search field and custom array controller to filter a collection of objects.
Although you typically use a search field in conjunction with a table view to implement this style of interface,
the tasks discussed in this article are not limited to these components.
Overriding arrangeObjects:
The arrangeObjects: method sorts the content of the array controller using the current sort descriptors.
To restrict the displayed content to a subset of the content you must override arrangeObjects:, create a
new array containing the subset, and then call the superclass’s implementation of arrangeObjects: to
provide any appropriate sorting.
The arrangeObjects: implementation in Listing 1 performs an anchored search of the title property. In an
anchored search, only those objects whose title property matches searchString are added to the returned
array.
Listing 1 Filtering implementation of arrangeObjects:
- (NSArray *)arrangeObjects:(NSArray *)objects {
if (searchString == nil) {
return [super arrangeObjects:objects];
}
NSMutableArray *filteredObjects = [NSMutableArray arrayWithCapacity:[objects
count]];
NSEnumerator *objectsEnumerator = [objects objectEnumerator];
id item;
while (item = [objectsEnumerator nextObject]) {
if ([[item valueForKeyPath:@"title"] rangeOfString:searchString
options:NSAnchoredSearch].location != NSNotFound) {
[filteredObjects addObject:item];
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
81
Filtering Using a Custom Array Controller}
}
return [super arrangeObjects:filteredObjects];
}
Updating the Search String
You implement the search: action method to invoke rearrangeObjects, which triggers the invocation of
the arrangeObjects: method.
Listing 2 shows an implementation of a search: action. The searchString is set to the string value of the
sender object, and rearrangeObjects is called.
Listing 2 Updating searchString
- (void)search:(id)sender {
// set the search string by getting the stringValue
// from the sender
[self setSearchString:[sender stringValue]];
[self rearrangeObjects];
}
- (void)setSearchString:(NSString *)aString
{
[aString retain];
[searchString release];
searchString=aString;
}
@end
The search: action is invoked by an NSSearchField. The target of the search field is set to the array controller,
and the action to the search: method. The search field should be configured so that it does not send the
whole string as it changes. Turning this option off causes the search: method to be invoked each time that
a keystroke occurs in the search field.
Filtering Using a Custom Array Controller
Updating the Search String
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
82The Cocoa bindings controller classes only provide key-value observing notifications for selected properties.
This article enumerates the key-value-observing compliant properties for each class.
Important: The Cocoa bindings controller classes do not provide change values when sending key-value
observing notificationsto observers. It isthe developer’sresponsibility to query the controller to determine
the new values.
NSUserDefaultsController
NSUserDefaultsController is key-value-observing compliant for the following properties:
● appliesImmediately
● defaults
● hasUnappliedChanges
●
initialValues
● values
NSObjectController
NSObjectController is key-value-observing compliant for the following properties:
● canAdd
● canRemove
● content
●
isEditable
● objectClass
●
selectedObjects
●
selection
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
83
Controller Key-Value Observing ComplianceNSArrayController
NSArrayController is key-value-observing compliant for the following properties:
● alwaysUsesMultipleValuesMarker
● arrangedObjects
● avoidsEmptySelection
● canAdd
● canInsert
● canRemove
● canSelectNext
● canSelectPrevious
● clearsFilterPredicateOnInsertion
● content
●
filterPredicate
●
isEditable
● preservesSelection
●
selectedObjects
●
selection
●
selectionIndex
●
selectionIndexes
●
selectsInsertedObjects
●
sortDescriptors
NSTreeController
NSTreeController is key-value-observing compliant for the following properties:
● alwaysUsesMultipleValuesMarker
● arrangedObjects
● avoidsEmptySelection
● canAdd
● canAddChild
Controller Key-Value Observing Compliance
NSArrayController
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
84● canInsert
● canInsertChild
● canRemove
● canSelectNext
● canSelectPrevious
● content
●
isEditable
● preservesSelection
●
selectedObjects
●
selection
●
selectionIndexPath
●
selectionIndexPaths
●
selectsInsertedObjects
●
sortDescriptors
Note: The value returned by NSTreeController's arrangedObjects method is opaque. You should
only observe this property to determine that a change has occurred.
Controller Key-Value Observing Compliance
NSTreeController
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
85Applications that use Cocoa bindings can provide a challenge when attempting to troubleshoot problems.
This article outlines some of the common issues encountered in applications that use Cocoa bindings and
provides clues as to correcting the problem.
My collection controller isn’t displaying the current data
This is typically due to your application modifying the collection content in a manner that is not
key-value-observing compliant. Modifying an array using addObject: or removeObject: is not sufficient.
You must either implement the indexed accessors for the collection key in your model class, or ask the model
for a key-value-observing aware collection proxy using one of the following methods:
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath
These return collection proxy objectsthat you can then use with the NSMutableArray or NSMutableSet mutation
methods, and the appropriate key-value observing notifications are sent.
You can also modify the contents through the collection controller using the methods described in
"Programmatically Modifying a Controller’s Contents" (page 41).
Changing the value in the user interface programmatically is not
reflected in the model
If you change the value of an item in the user interface programmatically, for example sending an NSTextField
a setStringValue: message, the model is not updated with the new value.
This is the expected behavior. Instead you should change the model object using a key-value-observing
compliant manner.
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
86
Troubleshooting Cocoa BindingsChanging the value of a model property programmatically is not
reflected in the user interface
If changes made to a model value programmatically are not being reflected in the user interface, this typically
indicatesthat the model object is not key-value-observing compliant for the property, or that you are modifying
the value in a manner that is bypassing key-value observing. You should ensure that:
● The model class has automatic key-value observing enabled or implements manual key-value observing
for the property.
● That you are changing the value using an accessor method, or using a key-value-coding compliant method.
Changing the value of an instance variable directly does not provide key-value observing change
notifications.
●
If your model property is a collection, that you're modifying the content in a key-value-observing compliant
manner. See "My collection controller isn’t displaying the current data" (page 86) for more information.
Binding to the incorrect key path
A common source of problems when using bindings is that a binding is established to the wrong key path. It
is easy to missthe debug messagesin the run log or console when the application doesn’t behave as expected.
The following is an example of a debug message that is output to the run log or console:
2005-05-10 03:53:29.764 VuesActivity[2216] *** NSRunLoop ignoring exception '[ valueForUndefinedKey:]:
this class is not key value coding-compliant for the key named.' that raised during
posting of delayed perform
with target 37f6d0 and selector 'invokeWithTarget:'
This message indicates that the instance of the Member class is not key-value-coding compliant for the key
"named". In this case the error is that the correct Member key is in fact "name".
You can get more information about an error by setting the bindings debug level default. This can be done
on the command line as follows:
defaults write com.yourdomain.yourapplication NSBindingDebugLogLevel 1
You can also set the debugging level in Xcode:
1. Select the application executable in the Executables group.
Troubleshooting Cocoa Bindings
Changing the value of a model property programmatically is not reflected in the user interface
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
872. Choose the Get Info menu item in the File menu.
3. Select the Arguments tab in the executables info panel.
4. Add -NSBindingDebugLogLevel 1 to the “Arguments to be passed at launch” list.
Aftersetting the debug level default, the same problem that generated the above error message now generates
the following error:
2005-05-10 04:02:32.146 VuesActivity[2241] Cocoa Bindings: Error accessing value
for key path selection.named
of object [object class: Member, number of selected
objects: 1]
(from bound object ): [
valueForUndefinedKey:]: this class
is not key value coding-compliant for the key named.
This provides more information that can aid you in tracking down which item in Interface Builder has been
bound to the wrong key path. In this case it indicates that the binding is between an NSTextField and an
NSArrayController with an object class of Member, using the selection.named key path.
With the binding debug level set to 1, any binding made to a non-existent key will cause a debug message to
be printed to the console, even if the binding has disabled the “Raises For Not Applicable Keys” option.
Currently only a debug level of 1 is supported.
Note: The bindings error messages are logged after the exception is raised. If your application is
set to break on [NSException raise] you will not see the error message in the run log or gdb. Be sure
to check your .gdbinit file for a breakpoint if you still do not see the bindings error messages.
The bindings of my custom view are disabled in Interface Builder
If you implement a subclass of NSView and create an Interface Builder palette for that subclass, your subclass’
implementation of bind:toObject:withKeyPath:options: must call the super implementation. If you
don’t when you establish a binding in Interface Builder, the binding values disappear and the binding names
become disabled and uneditable.
Troubleshooting Cocoa Bindings
The bindings of my custom view are disabled in Interface Builder
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
88An uneditable/visible/disabled NSTextField becomes
editable/hidden/enabled
If you disable editing for an NSTextField in Interface Builder, and then establish a binding to it, you may find
that the field then becomes editable when your application isrunning. Thisistypically due to the value binding
having the “Conditionally Sets Editable” binding option enabled.
Similarly, if a control becomes hidden or enabled, you should check that the “Conditionally Sets Hidden” and
“Conditionally Sets Enabled” options of the value binding have the expected settings.
Binding a control to a value that is an unsigned int causes an
exception
Key-value coding handlesthe conversion of NSNumbers and NSValuesto some well-known scalar and structure
types, but unsigned int is not currently supported. In the context of bindings, you should consider transforming
the value using an NSValueTransformer, or in the specific case of NSTextField, NSFormatter.
The view bound to an NSTreeController is not displaying data
This is often caused because the NSTreeController’s children key path has not been set. You can verify that
this is the cause by checking the console or run log for debug messages. See "Traversing Tree Content with
an NSTreeController" (page 39) for more information.
2005-06-23 02:46:53.198 MyApplication[6777] Cocoa Bindings: Cannot perform operation
if childrenKeyPath is nil.
A table column containing NSPopUpButtonCell itemsis notsortable
Tableview columns that contain NSPopUpButtonCell, or any cell type other than NSTextField can’t be sorted
if the cell’s selection is bound using the selectedObject or selectedObjects bindings. If the selection is
bound to selectedValue, selectedTag or selectedIndex, the column is sortable.
Troubleshooting Cocoa Bindings
An uneditable/visible/disabled NSTextField becomes editable/hidden/enabled
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
89This table describes the changes to Cocoa Bindings Programming Topics.
Date Notes
Added warning about lack of key-value observing change values to
Controller Key-Value Observing Compliance.
2009-03-08
2007-05-22 Clarified why you would use a controller.
2006-07-24 Corrected minor typos.
2006-06-28 Updated the prerequisite section in the introduction.
2006-05-23 Clarified Figure 3 in "Bindings Message Flow."
Corrected the NSBindingDebugLogLevel flag in the "Troubleshooting
Cocoa Bindings."
2006-03-08
Added "Controller Key-Value Observing Compliance " article. Added
additional troubleshooting information. Corrected minor typos.
2005-08-11
Added four new articles on controller content, controller selection,
message flow between model-view-controller, and troubleshooting Cocoa
bindings. Made other minor changes.
2005-07-07
Corrected minor typo. Added retain to example code in "How Do Bindings
Work?".
2005-04-29
2004-08-31 Corrected minor typos.
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
90
Document Revision HistoryDate Notes
This version represents a major revision of Cocoa Bindings. "What Are
Cocoa Bindings?" (page 10) and "How Do Bindings Work?" (page 23)
replace the “Why Use Cocoa Bindings?” and “Support for the MVC
Paradigm” articles. The “How to Use Bindings” article was removed. "User
Defaults and Bindings" (page 61) was added to the topic. "Filtering Using
a Custom Array Controller" (page 81) was edited to remove model
dependencies. “Example: Currency Converter With Bindings” was moved
to a separate book Developing Cocoa Applications Using Bindings: A
Tutorial . “Example: Enhanced Currency Converter” and
“Example:Preferences Pane” were removed from the topic.
2004-04-20
Added a link to Cocoa Bindings Reference from "Introduction to Cocoa
Bindings Programming Topics" (page 7). Corrected a bug in "Filtering
Using a Custom Array Controller" (page 81) that caused the NSSearchField
to loose focus.
2004-02-22
Renamed to Cocoa Bindings from Controller Layer. Made minor edits to
“Supporting the MVC Paradigm” and “Object Modeling”. Revised “Core
Classes and Protocols” and renamed it “How to Use Bindings”. Added four
new articles on how to create a master-detail interface, display images
using bindings, use pop-up menus to represent to-one relationships, and
filter an array controller’s arranged objects.
2003-12-16
Renamed to Controller Layer from The Cocoa Controller Layer. Revised
and reorganized all the conceptual articles in this topic. Enhanced the
tutorials by numbering the steps, adding a Converter model, and fixing
other errors.
2003-10-14
2003-08-07 First version of The Cocoa Controller Layer.
Document Revision History
2009-03-08 | © 2003, 2009 Apple Inc. All Rights Reserved.
91Apple Inc.
© 2003, 2009 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Cocoa, Mac, Numbers,
Objective-C, OS X, and Xcode are trademarks of
Apple Inc., registered in the U.S. and other
countries.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
Document-Based App
Programming Guide for
iOSContents
About Document-Based Applications in iOS 6
At a Glance 7
Document Objects Are Model Controllers 7
When Designing an Application, Consider Document-Data Format and Other Issues 7
Creating a Subclass of UIDocument Requires Two Method Overrides 7
An Application Manages a Document Through Its Life Cycle 7
An Application Stores Document Files in iCloud Upon User Request 8
An Application Ensures That Document Data is Saved Automatically 8
An Application Resolves Conflicts Between Different Document Versions 8
How to Use This Document 8
Prerequisites 9
See Also 9
Designing a Document-Based Application 10
Why Create a Document-Based Application? 10
A Document in iOS 11
A Document in iCloud Storage 12
Properties of a UIDocument Object 12
Design Considerations for Document-Based Applications 13
Defining Object Relationships 13
Designing the User Interface 14
Choosing Types, Formats, and Strategies for Document Data 15
Document-Based Application Preflight 18
What You Must Do to Make a Document-Based Application 18
Creating and Configuring the Project 19
How iOS Identifies Your Application’s Documents 20
Declare a Document Type 20
Exporting the Document UTI 22
Creating a Custom Document Object 23
Declaring the Document Class Interface 23
Loading Document Data 24
Supplying a Snapshot of Document Data 26
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
2Storing Document Data in a File Package 27
Other Method Overrides You Might Make 28
Managing the Life Cycle of a Document 30
Setting the Preferred Storage Location for Document Files 30
Creating a New Document 31
Document Filename Versus Document Name 31
Composing the File URL and Saving the Document File 31
Opening and Closing a Document 33
Discovering an Application’s Documents 33
Downloading Document Files from iCloud 36
Opening a Document 36
Closing a Document 38
Moving Documents to and from iCloud Storage 39
Getting the Location of the iCloud Container Directory 39
Moving a Document to iCloud Storage 40
Removing a Document from iCloud Storage 42
Monitoring Document-State Changes and Handling Errors 43
Deleting a Document 45
Change Tracking and Undo Operations 48
How UIKit Saves Document Data Automatically 48
Implementing Undo and Redo 48
Implementing Change Tracking 50
Resolving Document Version Conflicts 51
Learning About Document Version Conflicts 51
Strategies for Resolving Document Version Conflicts 51
How to Tell iOS That a Document Version Conflict Is Resolved 52
An Example: Letting the User Pick the Version 53
Document Revision History 56
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
3
ContentsFigures, Tables, and Listings
Designing a Document-Based Application 10
Figure 1-1 Document file, document object, and model objects managed by the document object 12
Figure 1-2 A view controller manages both a document object and the view that presents document data
14
Document-Based Application Preflight 18
Figure 2-1 iOS looking up a document UTI from the extension of its file URL 20
Figure 2-2 Specification of a document type in Xcode 21
Figure 2-3 Exporting a custom document UTI in Xcode 22
Table 2-1 Properties for defining a document type (CFBundleDocumentTypes) 21
Table 2-2 Properties for exporting a document UTI (UTExportedTypeDeclarations) 22
Creating a Custom Document Object 23
Figure 3-1 Structure of a file package 27
Listing 3-1 Document subclass declarations (NSData) 23
Listing 3-2 Document subclass declarations (NSFileWrapper) 24
Listing 3-3 Loading a document’s data (NSData) 25
Listing 3-4 Loading a document’s data (NSFileWrapper) 25
Listing 3-5 Returning a snapshot of document data (NSData) 26
Listing 3-6 Returning a snapshot of document data (NSFileWrapper) 26
Managing the Life Cycle of a Document 30
Table 4-1 UIDocumentState constants 43
Listing 4-1 Getting a URL to the application’s Documents directory in the local sandbox 32
Listing 4-2 Saving a new document to the file system 32
Listing 4-3 Getting the locations of documents stored locally and in iCloud storage 34
Listing 4-4 Collecting information about documents in iCloud storage 35
Listing 4-5 Responding to a request to open a document 37
Listing 4-6 Opening a document 38
Listing 4-7 Closing a document 39
Listing 4-8 Getting the iCloud container directory URL 40
Listing 4-9 Moving a document file to iCloud storage from local storage 41
Listing 4-10 Moving a document file from iCloud storage to local storage 42
Listing 4-11 Adding an observer of the UIDocumentStateChangedNotification notification 43
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
4Listing 4-12 Evaluating the current document state 44
Listing 4-13 Updating a document’s user interface to reflect its state 45
Listing 4-14 Deleting a selected document 46
Change Tracking and Undo Operations 48
Listing 5-1 Implementing undo and redo for a text field 49
Listing 5-2 Updating the change count of a document 50
Resolving Document Version Conflicts 51
Listing 6-1 Detecting a conflict in document versions 53
Listing 6-2 Showing the user interface for resolving document version conflicts 54
Listing 6-3 Resolving a document version conflict 54
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
5
Figures, Tables, and ListingsThe UIKit framework offers support for applications that manage multiple documents, with each document
containing a unique set of data that is stored in a file located either in the application sandbox or in iCloud.
iCloud
Daemon
iCloud
Daemon
Central to this support is the UIDocument class, introduced in iOS 5.0. A document-based application must
create a subclass of UIDocument that loads document data into its in-memory data structures and supplies
UIDocument with the data to write to the document file. UIDocument takes care of many details related to
document management for you. Besides its integration with iCloud, UIDocument reads and writes document
data in the background so that your application’s user interface does not become unresponsive during these
operations. It also saves document data automatically and periodically, freeing your users from the need to
explicitly save.
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
6
About Document-Based Applications in iOSAt a Glance
Although a document-based application is responsible for a range of behaviors, making an application
document-based is usually not a difficult task.
Document Objects Are Model Controllers
In the Model-View-Controller design pattern, document objects—that is, instances of subclasses of
UIDocument—are model controllers. A document object manages the data associated with a document,
specifically the model objects that internally represent what the user is viewing and editing. A document
object, in turn, is typically managed by a view controller that presents a document to users.
Relevant Chapter: “Designing a Document-Based Application” (page 10)
When Designing an Application, Consider Document-Data Format and Other
Issues
Before you write a line of code you should consider aspects of design specific to document-based applications.
Most importantly, what is the best format of document data for your application, and how can you make that
format work for your application in iOS and Mac OS X? What is the most appropriate document type?
You also need to plan for the view controllers(and views) managing such tasks as opening documents, indicating
errors, and moving selected documents to and from iCloud storage.
Relevant Chapters: “Designing a Document-Based Application” (page 10), “Document-Based Application
Preflight” (page 18)
Creating a Subclass of UIDocument Requires Two Method Overrides
The primary role of a document object is to be the “conduit” of data between a document file and the model
objects that internally represent document data. It gives the UIDocument class the data to write to the
document file and, after the document file isread, it initializesits model objects with the data that UIDocument
givesit. To fulfill thisrole, yoursubclass of UIDocument must override the contentsForType:error: method
and the loadFromContents:ofType:error: method, respectively.
Relevant Chapter: “Creating a Custom Document Object” (page 23)
An Application Manages a Document Through Its Life Cycle
An application is responsible for managing the following events during a document’s lifetime:
● Creation of the document
● Opening and closing the document
About Document-Based Applications in iOS
At a Glance
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
7● Monitoring changes in document state and responding to errors or version conflicts
● Moving documents to iCloud storage (and removing them from iCloud storage)
● Deletion of the document
Relevant Chapter: “Managing the Life Cycle of a Document” (page 30)
An Application Stores Document Files in iCloud Upon User Request
Applications give their users the option for putting all document files in iCloud storage or all document files
in the local sandbox. To move document files to iCloud, they compose a file URL locating the document in an
iCloud container directory of the application and then call a specific method of the NSFileManager class,
passing in the file URL. Moving document filesfrom iCloud storage to the application sandbox follows a similar
procedure.
Relevant Chapter: “Managing the Life Cycle of a Document” (page 30)
An Application Ensures That Document Data is Saved Automatically
UIDocument follows the saveless model and automatically saves a document’s data at specific intervals. A
user usually never has to save a document explicitly. However, your application must play its part in order for
the saveless model to work, either by implementing undo and redo or by tracking changes to the document.
Relevant Chapter: “Change Tracking and Undo Operations” (page 48)
An Application Resolves Conflicts Between Different Document Versions
When documents are stored in iCloud, conflicts between versions of a document can occur. When a conflict
occurs, UIKit informs the application about it. The application must attempt to resolve the conflict itself or
invite the user to pick the version he or she prefers.
Relevant Chapter: “Resolving Document Version Conflicts” (page 51)
How to Use This Document
Before you start writing any code for your document-based application, you should at least read the first two
chapters, “Designing a Document-Based Application” (page 10) and “Document-Based Application
Preflight” (page 18). These chapters talk about design and configuration issues, and give you an overview of
the tasks required for well-designed document-based applications
About Document-Based Applications in iOS
How to Use This Document
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
8Prerequisites
Before you read Document-Based Application Programming Guide for iOS you should become familiar with
the information presented in iOS App Programming Guide .
See Also
The following documents are related in some way to Document-Based Application Programming Guide for iOS :
● Uniform Type Identifiers Overview and the related reference discuss Uniform Type Identifiers (UTIs), which
are the primary identifiers of document types.
● FileMetadataSearchProgrammingGuide describeshowtoconductsearchesusingtheNSMetadataQuery
class and related classes. You use metadata queries to locate an application’s documents stored in iCloud.
●
iCloud Design Guide provides an introduction to iCloud document support.
About Document-Based Applications in iOS
Prerequisites
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
9The UIDocument class, introduced in iOS 5.0, plays the principal role in document-based applications for iOS.
It is an abstract base class—meaning that for you to have something useful, you must create a subclass of it
that is tailored to the needs of your application. The application-specific code that you add to the subclass
augments what UIDocument makes possible: efficient behavior of documents in a mobile environment
integrated with iCloud storage.
Why Create a Document-Based Application?
When you have an idea for any application and sit down to design it, you must evaluate a multitude of options.
What style of application should you use (master-detail, page-based utility, OpenGL game, and so on)? Will
the application do its own drawing, will it respond to gestures or touch events, and will it incorporate audiovisual
assets? What will the data model be—for example, should the application use Core Data? The decision whether
to make your application document-based might seem to complicate that series of choices, but it really boils
down to how you anticipate people will use your application.
Document-based applications are ideal, even necessary, when users expect to enter and edit content in a visual
container and store that content under a name they specify. Each container of information—a document—is
unique. You are no doubt familiar with several types of document-based applications found in both desktop
and mobile systems. To name a few, there are word processors, spreadsheets, and drawing programs. With
the iCloud technology, you have an even more compelling reason to make your application document-based.
Your users, for example, could create a document using your application on a OS X desktop system and then
later, without having to do any syncing or copying, edit that document on an iPad using the iOS version of
your application. This feature gives them an additional incentive to buy your application.
When you adopt the UIDocument approach to document-based applications, your application gets a lot of
behavior “for free” or with minimal coding effort on your part.
●
Integration with iCloud storage. The UIDocument object coordinates all reading and writing of document
data from and to iCloud storage. It does this by adopting the NSFilePresenter protocol and by calling
methods of the NSFileCoordinator and NSFileManager classes.
● Background writing and reading of document data. If your application reads and writes a document
synchronously, it can become momentarily unresponsive. The UIDocument object avoids this problem
by reading and writing document data asynchronously on a background dispatch queue.
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
10
Designing a Document-Based Application● Savelessmodel. In the saveless model, a user of a document-based application rarely hasto save documents
explicitly; The UIDocument object saves document data automatically at intervals optimized for what the
user is doing with the document. You make your document-based application “saveless” by implementing
undo management or change tracking (see “Change Tracking and Undo Operations” (page 48) for
information).
● Safe saving. The UIDocument object saves document data safely; consequently, if some external event
interrupts a save operation, document data won’t be left in an inconsistent state. The object implements
safe saving by first writing the newest version of a document to a temporary file and then replacing the
current document file with it.
● Support for handling errors and version conflicts. When the UIDocument object detects a conflict
between different versions of a document, it notifies the application. The application can then attempt to
resolve the conflict itself, or it can ask the user to pick the desired document version. The UIDocument
object also notifies an application when a save operation does not succeed. “Managing the Life Cycle of
a Document” (page 30) describes how to observe these notifications; “Resolving Document Version
Conflicts” (page 51) discusses strategies for dealing with document-version conflicts.
A Document in iOS
Although the broad definition of a document is a “container of information,” that container can be viewed in
several ways. To a user, a document is the text, images, shapes, and other forms of information that he or she
creates, edits, and saves under a unique name. A document can also refer to a document file: the persistent,
on-disk representation of document data. And a document can mean a UIDocument object that represents
and manages the in-memory representation of that same data.
A document object also plays a key role in converting document data between its representation on disk to
its representation in memory. It cooperates with the UIDocument class in the writing of document data to a
file and the reading of that data. For writing, a document typically provides a snapshot of data that can be
Designing a Document-Based Application
A Document in iOS
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
11written to the document file; for reading, it receives data and initializes the document’s model objects with it.
A document, in a sense, is a conduit between data stored in a file and the internal representation of that data.
Figure 1-1 illustrates these relationships.
Figure 1-1 Document file, document object, and model objects managed by the document object
Document
Model
Model Model
Model
Document
File
A Document in iCloud Storage
In iCloud, files reside locally in a container directory that is associated with an application. That directory has
an internal structure that includes, most importantly, a Documents subdirectory. In the iCloud scheme, files
and file packages written to the Documents subdirectory are considered document files—even if they don’t
originate from document-based applications. Filesthat are written to other locationsin the container directory
are considered data files.
Document objects are file presenters because the UIDocument class adopts the NSFilePresenter protocol.
A file presenter is used together with NSFileCoordinator objects to coordinate access to a file or directory
among the objects of an application and between an application and other processes. A file presenter is
involved in other clients’ access of the same presented file or directory.
When users ask to store document files in iCloud storage, a document-based application should save those
files (including file packages) in the Documents subdirectory of the iCloud container directory. See “Managing
the Life Cycle of a Document” (page 30) for specifics. For more information about iCloud mobile containers,
see iCloud Design Guide .
Properties of a UIDocument Object
A document object has several defining properties, most of which relate to its role as a manager of document
data. These properties are declared by the UIDocument class.
Designing a Document-Based Application
A Document in iOS
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
12● File URL. A document must have a location where it can be stored, whether that location is in the local
file system or in iCloud storage. The fileURL property identifies this location. When you create a
UIDocument object, you must specify a file URL as the parameter of the initWithFileURL: initializer
method of UIDocument.
● Document name. A UIDocument object obtains a default document name from the filename component
of the file URL and stores it in the localizedName property. You can override the getter method of this
property to provide a custom, localized document name.
Note: The display name of a document does not have to correspond to the filename of the
document. In addition, usersshould not be required to specify a document name. See “Creating
a New Document” (page 31) for more information on this subject.
● File type. The file type is a Uniform Type Identifier (UTI) derived from the extension of the file URL and
assigned to the fileType property. For more information, see “How iOS Identifies Your Application’s
Documents” (page 20).
● Modification date. The date the document file was last modified. This value is stored in the
fileModificationDate property. It can be use for (among other things) resolving document-version
conflicts.
● Document state. A document is in one of several possible states during its runtime life. These states can
indicate, for example, that there was an error in saving the document or that there are conflicting document
versions. UIDocument storesthe current documentstate in the documentState property. For information
about observing changes in document state, see “Monitoring Document-State Changes and Handling
Errors” (page 43).
Design Considerations for Document-Based Applications
Before you write a line of code for your document-based application, it’s worth your while to think about a
few design issues.
Defining Object Relationships
As with all applications, you should devise an overall design for the objects of your document-based application
that is based on the Model-View-Controller design pattern (MVC). Although the following MVC design for
documents is recommended, you are free to come up with your own object-relationship designs.
Designing a Document-Based Application
Design Considerations for Document-Based Applications
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
13In MVC terms, a document is a model controller; it “owns” and manages the model objects that represent the
document’s content. The document object itself is owned and managed by a view controller. The view controller,
by definition, also manages a view that presents the content managed by the document object. It is thus a
mediating controller in this network of relationships, obtaining the data it needs to present in the view from
the document object and passing data entered or changed by usersto the document object. Figure 1-2 depicts
these object relationships.
Figure 1-2 A view controller manages both a document object and the view that presents document data
Sed ut perspiciatis unde
omnis iste natus error sit
voluptatem accusantium
doloremque laudantium,
totam rem aperiam, eaque
ipsa quae ab illo inventore
veritatis et quasi architecto
beatae vitae dicta sunt
explicabo. Nemo enim
ipsam voluptatem quia
voluptas sit aspernatur aut
odit aut fugit, sed quia
consequuntur magni
dolores eos qui ratione
voluptatem sequi nesciunt.
manages manages
View controller
Document
Model
Model
Model
Just as a view controller embeds its view, a view controller might embed the document object as a declared
property. When your application instantiates the view controller, it initializes it with the document object or
with the document’s file URL (from which the view controller itself can create the document object).
Of course, a document-based application will have other view controllers (with their views) and possibly other
model objects. To get an idea of what other view controllers might be required, see “Designing the User
Interface” (page 14).
Designing the User Interface
Neither UIKit nor the developer tools provide any support for the user interface of a document-based application.
For instance, there is no UIDocumentView class or UIDocumentViewController class and there is no
standard interface for selecting documents. Rather than regret the absence, take it as an opportunity to create
a user interface for your document-based application that makes it stand out from the competition.
Designing a Document-Based Application
Design Considerations for Document-Based Applications
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
14Nonetheless, all document-based applications should enable their users to do certain things that require user
interface elements; these include the following:
● Viewing and editing a document
● Creating a new document
● Selecting a document from a list of documents owned by the application
● Opening, closing, and deleting a selected document
● Putting a selected document in iCloud storage (and removing a selected document from iCloud storage)
●
Indicating error conditions, including document-version conflicts
● Undoing and redoing changes (recommended but not required)
When you design your application, be sure to include the view controllers, views, and controlsthat are necessary
to implement these actions.
Choosing Types, Formats, and Strategies for Document Data
When designing the data model for your document-based application, it’s critical that you ask and answer the
following questions:
What is the type of my document?
A document must have a document type, a collection of information that characterizes the document and
associatesit with an application. A document type has a name, an icon (optional), a handler rank, and a Uniform
Type Identifier (UTI) that is paired with one or more filename extensions. For an application to edit the same
document in iOS and in OS X, the document-type information should be consistent.
“Creating and Configuring the Project” (page 19) explains how to specify document typesin a document-based
iOS application. See Uniform Type Identifiers Overview for a description of UTIs and Uniform Type Identifiers
Reference for descriptions of common Uniform Type Identifiers.
How should I represent the document data that is written to a file?
You have basically three choices:
● Core Data (database). Core Data is a technology and framework for object-graph management and
persistence. It can use the built-in SQLite data library as a database system. The UIManagedDocument
class, a subclass of UIDocument, is intended for document-based applications that use Core Data.
● Supported object formats. UIDocument supports the NSData and NSFileWrapper objects as native
typesfor document-data representation. NSData isintended for flat files, and NSFileWrapper isintended
for file packages, which are directories that iOS treats as a single file. If you give UIDocument one of these
objects, it saves it to the file system without further involvement on your part.
Designing a Document-Based Application
Design Considerations for Document-Based Applications
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
15● Custom object formats. If you want the document data written to a file to be of a type other than NSData
or NSFileWrapper, you can write it to the file yourself in an override of a specific UIDocument method.
Keep in mind that your code will have to duplicate what UIDocument does for you, and so you must deal
with greater complexity and a greater possibility of error. See UIDocument Class Reference for further
information.
Why would I want to store my document data in a database instead of a file?
If the data set managed by your document object is primarily a large object graph but your application uses
only a subsection of that graph at any time, then UIManagedDocument and Core Data are a good option.
Core Data brings many benefits to a document-based application:
●
Incremental reading and writing of document data
● Support for undo and redo operations
● Automatic support for resolving document-version conflicts
● Data compatibility for cross-platform applications
The “downside” to Core Data isthat it is a complex technology; it takestime to become familiar with its concepts,
classes, and techniques. To learn more, read Core Data Programming Guide and UIManagedDocument Class
Reference .
Which of the supported object formats (NSData or NSFileWrapper) is best for my application?
Whether you use an NSData or NSFileWrapper object for document data depends on how complex that
data is and on whether part of your document’s model-object graph can be written out separately to a file.
For example, if your document data is plain text and nothing else, then NSData is a suitable container for it.
If, however, the data has multiple components—for example, text and images—use NSFileWrapper methods
to create a file package for the data.
File wrapper objects—and file packages which they represent—offersome advantages over binary data objects.
● File wrappers support incremental saving. In contrast with a single binary data object, if a file wrapper
contains your document data—for example, text and an image—and the text changes, only the file
containing the text has to be written out to disk. This capability results in better performance.
● File wrappers (and file packages) make it easier to version documents. A file package, for example, can
contain a property list file that holds version information and other metadata about a document.
“Storing Document Data in a File Package” (page 27) discusses(and illustrates) how you use an NSFileWrapper
object to represent document data in a form that can be written out as a file package.
Can I archive my model-object graph and have that NSData object written to the document file?
Designing a Document-Based Application
Design Considerations for Document-Based Applications
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
16Yes, that is possible, but some of the caveats previously mentioned apply. If any part of your document’s
model-object graph can be written to a separate file, don’t archive that part. Instead, store the NSData archive
and the partitioned-out items as separate components of a file package.
What if my document file is very large?
If the data stored in a document file can be large, you might have to write and read the data incrementally to
ensure a good user experience. There are a few approaches you might take:
● Use UIManagedDocument. Remember, Core Data gives you incremental reading and writing “for free.”
● Store the separable components of your document data in a file package.
● Override lower-level methods of UIDocument and do the incremental reading and writing of data yourself.
(See UIDocument Class Reference for particulars.) You should always use an API for incremental reading
and writing that is appropriate to the data type; for example, the AV Foundation framework has methods
for incremental reading of large multimedia files.
What should I be thinking about if I want my documents to be editable on both platforms?
Your application will have a competitive advantage if your users can create and edit documents both on iOS
mobile devices (iPhone, iPad, and iPad touch) and on OS X desktop systems. But for this to be possible, the
format of the document data on both platforms should be compatible. Some important considerations for
document-data compatibility are:
● Some technologies are available on one platform but not the other. For example, if you’re using RTF as a
document format in OS X, that format won’t work in iOS because its text system doesn’t support rich text.
● The corresponding classesin each platform are not compatible. Thisis especially true with colors(UIColor
and NSColor), images (UIImage and NSImage), and Bezier paths (UIBezierPath and NSBezierPath).
NSColor objects, for example, are defined in terms of a color space (NSColorSpace), but there is no
color space class in UIKit.
If you define a document property with one of these classes, you must devise a way (when preparing the
document data for writing) to convert the property into a form that can be accurately reconstituted on
the other platform. One way to do this is to “drop down” to a lower-level framework that is shared by both
platforms. For example, UIColor defines a CIColor property holding a Core Image object representing
the color; on the OS X side, your application can create an NSColor object from the CIColor object using
the colorWithCIColor: class method.
● The default coordinate system for each platform is different, and this difference can affect how content is
drawn. (See “Default Coordinate Systems and Drawing in iOS” in Drawing and Printing Guide for iOS for a
discussion of this topic).
●
If you archive a document’s model-object graph, partially or entirely, then you might have to perform
platform-sensitive conversions using NSCoder methods when you encode and decode the model objects.
Designing a Document-Based Application
Design Considerations for Document-Based Applications
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
17For most developers, making a document-based application doesn’t require much more effort than making
an application that isn’t document based. The basic difference is that you must create a custom subclass of
UIDocument and then manage a document through the phases of its runtime life, including its integration
with iCloud storage. This chapter outlines those document-specific tasks performed by most applications and
describes the general steps for creating and configuring a document-based application project in iOS.
What You Must Do to Make a Document-Based Application
To create a document-based application, you should complete the following tasks:
● Create a custom subclass of UIDocument that provides the UIKit framework with a snapshot of document
data and that initializes the document’s model objects from the contents of the document file.
“Creating a Custom Document Object” (page 23) describesthe required method overrides and, if document
data isto be stored as a file package, explains how to go about using NSFileWrapper objectsfor document
data.
● Allow users to create new documents and select and open existing ones. You should also implement the
complementary tasks of closing documents and deleting selected documents.
Note that the follow-on task to creating or opening a document is displaying the document’s content in
a view.
“Creating a New Document” (page 31), “Opening and Closing a Document” (page 33), and “Deleting a
Document” (page 45) describe the requirements and procedures for these tasks. These discussions also
include examples of managing the display of document data.
●
Implement undo management or change tracking to enable the automatic saving of document data
(saveless model).
See “Change Tracking and Undo Operations” (page 48) for details.
● Put documents—typically those selected by users—into iCloud storage. Also remove documents from
iCloud storage when users request this.
“Moving Documents to and from iCloud Storage” (page 39) discusses these procedures.
● Observe notifications of changes in document state and, if an error occurs, respond appropriately.
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
18
Document-Based Application Preflight“Monitoring Document-State Changes and Handling Errors” (page 43) describes the general procedure
for doing this.
●
If conflicts between different versions of a document occur, notify the user and offer ways to resolve the
conflicts.
“Resolving Document Version Conflicts” (page 51) describes how to notify users of version conflicts and
discusses strategies for resolving these conflicts.
You can also add extra features to your document-based application, such as the capability for printing the
document, spell-checking it, or emailing it to others.
If your application has advanced requirements—for example, incremental reading and writing of large document
files or dealing with document-data formats other than the supported ones—see UIDocument Class Reference .
All of these advanced tasks involve overriding UIDocument methods.
Creating and Configuring the Project
When you create the Xcode project for your document-based application, choose a suitable template. (Note
that there is no template specifically for document-based applications.) Generally, you want the first view of
the application to be one in which users can choose existing documents and create new ones. Because the
Master-Detail Xcode template is suitable for this purpose, it is used in the code examples throughout this
document. Thistemplate gives you an initial table view for the iPhone and a split view for the iPad. Your project
should use a storyboard, so be sure to select this option.
Note: If you are using Core Data to manage document data, be sure to select the “Use Core Data”
option in the New Project assistant.
Based on the design for your application (see “Designing a Document-Based Application” (page 10)), create
the view-controller subclasses that your application requires. All you need are minimally declared header and
source files at this point. Then, create the user interface of your application in the project storyboard (or
storyboards, if yours is a universal application); associate your custom view controllers with the view-controller
placeholders in the storyboard.
Next, you need to configure the project for documents by specifying, in the Xcode target settings, the type or
types of the documents that the application knows about.
Document-Based Application Preflight
Creating and Configuring the Project
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
19How iOS Identifies Your Application’s Documents
The most important attribute of a document object in iOS is its file URL (fileURL). The file URL is important,
among other reasons, because it tells iOS which applications understand the document’s format. The file URL
ends with an extension (for example, html) and this extension is matched with a Uniform Type Identifier (for
example, public.html). The Uniform Type Identifier (UTI) is the principal identifier of document type. Using
the extension, UIDocument looks up the document-type UTI (as shown in Figure 2-1) and assigns it to the
fileType property. Unlike document-based applications in OSX, those in iOS don’t need to associate the
UIDocument subclass with the document type.
Figure 2-1 iOS looking up a document UTI from the extension of its file URL
File
/Documents/foo.mydoc
Application File System
Document
fileURL
CFBundleDocumentTypes
(Document Type 1)
CFBundleTypeExtensions
mydoc
LSItemContentTypes
com.acme.app.mydoc
...
A document-type UTI can be defined by the system; see “System-Declared Uniform Type Identifiers” in Uniform
Type Identifiers Reference for a list of these common identifiers. A document-based application can also define
its own proprietary UTI for its documents (and often does). If it does declare a custom UTI, it must also export
that UTI to make the operating system aware of it.
Declare a Document Type
To declare a document type in Xcode, start by clicking the Add button in the target’s Info settings and choose
Add Document Type from the pop-up menu. Click the triangle next to Untitled to disclose the property fields
and add the properties in Table 2-1.
Document-Based Application Preflight
Creating and Configuring the Project
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
20Table 2-1 Properties for defining a document type (CFBundleDocumentTypes)
Key Xcode field Value and comments
An array of UTIstrings. Only one istypically
specified per document type.
LSItemContentTypes Types
CFBundleTypeName Name An optional name for the document type.
An array of paths to icon image files in the
application bundle.
CFBundleType- Icon
IconFiles
An array of filename extensions paired with
the document UTI.
In “Additional document
type properties” table.
CFBundleTypeExtensions
Owner, Alternate, None. (Typically
Owner).
In “Additional document
type properties” table.
LSHandlerRank
If document data isstored in a file package,
set this property to YES. Otherwise omit.
In “Additional document
type properties” table.
LSTypeIsPackage
Formore information aboutthese keys,see “CFBundleDocumentTypes” in Information Property List Key Reference .
When you have finished entering the properties for a document type, The Document Types area of Xcode
should look similar to the example in Figure 2-2.
Figure 2-2 Specification of a document type in Xcode
An application could have multiple types of documents—for example, a word-processing application could
have a type for regular (blank) documents and another type for pre-formatted documents. For each type, you
need to go through the procedure given above .
Document-Based Application Preflight
Creating and Configuring the Project
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
21Exporting the Document UTI
If you define a custom UTI for your documents, you must also export it. To export a document type in Xcode,
start by clicking the Add button in the target’s Info settings and choose Add Exported UTI from the pop-up
menu. Click the triangle next to Untitled to disclose the property fields and add the properties in Table 2-2.
Note: The procedure for declaring and exporting UTIs is described in “Declaring New Uniform Type
Identifiers” in Uniform Type Identifiers Overview.
Table 2-2 Properties for exporting a document UTI (UTExportedTypeDeclarations)
Key Xcode field Value and comments
UITypeIdentifier Identifier The custom document UTI, a string.
The UTI that the custom document UTI conforms
to. If the data representation is a file package,
specify com.apple.package.
UTTypeConformsTo Conforms to
UTTypeDescription Description Description of the exported type (optional).
Create an array named
public.filename-extension. Then add as
items all extensions of the document file.
In “Additional
exported UTI
properties” table.
UTTypeTagSpecification
Formore information aboutthese keys,see “CFBundleDocumentTypes” in Information Property List Key Reference .
When you have finished entering the properties for a document type, The Exported UTIs area of Xcode should
look similar to the example in Figure 2-3.
Figure 2-3 Exporting a custom document UTI in Xcode
Document-Based Application Preflight
Creating and Configuring the Project
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
22A document-based application must have an instance of a subclass of UIDocument that represents and
manages document data. This chapter discusses the method overrides most applications need to make and
offers suggestions for overriding other methods. For the core override points—the
loadFromContents:ofType:error: and contentsForType:error: methods—examples are given for
both NSData and NSFileWrapper astypes of document data read from and written to a file.“Storing Document
Data in a File Package” (page 27) further explains how to use file-wrapper objects for document data.
You can override methods of UIDocument other than the ones discussed in this chapter to read and write
document data for particular purposes—for example, to write and read document data incrementally. However,
these more advanced overrides have more complex requirements and should be avoided if possible. See
UIDocument Class Reference for discussions of these overrides.
Declaring the Document Class Interface
In Xcode, add new Objective-C source and header files to your project, naming them appropriately (suggestion:
work “Document” into the name). In the header file, change the superclass to UIDocument and add properties
to hold the document data. In Listing 3-1, the document data is plain text, so an NSString property is all that
is needed to hold it. (The text will be converted to an NSData object that is written to the document file.)
Listing 3-1 Document subclass declarations (NSData)
@interface MyDocument : UIDocument {
}
@property(nonatomic, strong) NSString *documentText;
@end
Listing 3-2 illustrates set of declarations for another application that uses an NSFileWrapper object as the
data-representation type. (The code examples in the chapter alternate between the two applications.) Not
only is there a property to hold the file-wrapper object, there are properties to hold the text and image
components of the represented file package.
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
23
Creating a Custom Document ObjectListing 3-2 Document subclass declarations (NSFileWrapper)
@interface ImageNotesDocument : UIDocument
@property (nonatomic, strong) NSString* text;
@property (nonatomic, strong) UIImage* image;
@property (nonatomic, strong) NSFileWrapper *fileWrapper;
@property (nonatomic, weak) id delegate;
@end
@protocol ImageNotesDocumentDelegate
-(void)noteDocumentContentsUpdated:(ImageNotesDocument*)noteDocument;
@end
This code shows additional declarations for a delegate and the protocol that it adopts. The document object’s
view controller makes itself the delegate of the document object (and adopts the protocol) so that it can be
notified (via noteDocumentContentsUpdated: messages) of modifications to the document file. Listing
3-4 (page 25) shows when and how the noteDocumentContentsUpdated: message is sent.
Loading Document Data
When an application opens a document (at the user’srequest), UIDocument readsthe contents of the document
file and calls the loadFromContents:ofType:error: method, passing in an object encapsulating the
document data. That object can be an NSData object or an NSFileWrapper object. In your override of the
method, initialize the document’s internal data structures (that is, its model objects) from the contents of the
passed-in object.
The example in Listing 3-3 creates a string from the passed-in NSData object and assigns it to the
documentText property. It also informs its delegate (in this case, the document’s view controller) of the
updated document contents by invoking a protocol method. The motivation behind this delegation message
isthatthe loadFromContents:ofType:error:method is called not only asthe result of opening a document,
but also because of iCloud updates and reversion operations
(revertToContentsOfURL:completionHandler:).
Creating a Custom Document Object
Loading Document Data
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
24Listing 3-3 Loading a document’s data (NSData)
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError
**)outError {
if ([contents length] > 0) {
self.documentText = [[NSString alloc] initWithData:(NSData *)contents
encoding:NSUTF8StringEncoding];
} else {
self.documentText = @"";
}
if ([_delegate respondsToSelector:@selector(noteDocumentContentsUpdated:)]) {
[_delegate noteDocumentContentsUpdated:self];
}
return YES;
}
If you have more than one document type, check the typeName parameter; a different document type might
affect how your code handles the document-data object. If your code experiences an error that prevents it
from loading document data, return NO; optionally, you can return by reference an NSError object that
describes the error.
The example in Listing 3-4 handles document data in the form of an NSFileWrapper object. It simply assigns
this object to its property.
Listing 3-4 Loading a document’s data (NSFileWrapper)
-(BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError
**)outError {
self.fileWrapper = (NSFileWrapper *)contents;
if ([_delegate respondsToSelector:@selector(noteDocumentContentsUpdated:)]) {
[_delegate noteDocumentContentsUpdated:self];
}
return YES;
}
In this code, the method implementation does not extract the text and image components of the file wrapper
and assign them to their properties. That is done lazily in the getter methodsfor the text and image properties.
Creating a Custom Document Object
Loading Document Data
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
25Supplying a Snapshot of Document Data
When a document is closed or when it is automatically saved, UIDocument sends the document object a
contentsForType:error: message. You must override this method to return a snapshot of the document’s
data to UIDocument, which then writes it to the document file. Listing 3-5 gives an example of returning a
snapshot of document data in the form of an NSData object.
Listing 3-5 Returning a snapshot of document data (NSData)
- (id)contentsForType:(NSString *)typeName error:(NSError **)outError {
if (!self.documentText) {
self.documentText = @"";
}
NSData *docData = [self.documentText dataUsingEncoding:NSUTF8StringEncoding
allowLossyConversion:NO];
return docData;
}
If the documentText property has not yet been assigned any string value yet, it is assigned an empty string
before it’s used to create an NSData object.
Listing 3-6 shows an implementation of the same method that returns an NSFileWrapper object. Basically,
if a top-level (directory) file-wrapper object doesn’t exist, the code creates it; and if the two contained (regular
file) file-wrapper objects do not exist, the code createsthem from the values of the text and image properties.
Then, it returns the top-level file wrapper to UIDocument, which creates a file package in the file system. See
“Storing Document Data in a File Package” (page 27) for a more detailed explanation of file packages and
documents.
Listing 3-6 Returning a snapshot of document data (NSFileWrapper)
-(id)contentsForType:(NSString *)typeName error:(NSError **)outError {
if (self.fileWrapper == nil) {
self.fileWrapper = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:nil];
}
NSDictionary *fileWrappers = [self.fileWrapper fileWrappers];
if (([fileWrappers objectForKey:TextFileName] == nil) && (self.text != nil))
{
NSData *textData = [self.text dataUsingEncoding:TextFileEncoding];
Creating a Custom Document Object
Supplying a Snapshot of Document Data
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
26NSFileWrapper *textFileWrapper = [[NSFileWrapper alloc]
initRegularFileWithContents:textData];
[textFileWrapper setPreferredFilename:TextFileName];
[self.fileWrapper addFileWrapper:textFileWrapper];
}
if (([fileWrappers objectForKey:ImageFileName] == nil) && (self.image != nil))
{
@autoreleasepool {
NSData *imageData = UIImagePNGRepresentation(self.image);
NSFileWrapper *imageFileWrapper = [[NSFileWrapper alloc]
initRegularFileWithContents:imageData];
[imageFileWrapper setPreferredFilename:ImageFileName];
[self.fileWrapper addFileWrapper:imageFileWrapper];
}
}
return self.fileWrapper;
}
Storing Document Data in a File Package
A file package has an internal structure that is reflected in the methods of the NSFileWrapper class. A file
wrapper is a runtime representation of a file-system node, which is either a directory, a regular file, or a symbolic
link. As shown in Figure 3-1, a file package is a file-system node, typically a directory and its contents, that the
operating system treats as a single, opaque entity. It is similar in concept to a bundle.
Figure 3-1 Structure of a file package
File
File File
Directory
ArchivedObjects theImage.png
Creating a Custom Document Object
Storing Document Data in a File Package
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
27You programmatically compose a file package by creating a top-level directory file wrapper and then adding
to that container regular files and subdirectories, each represented by other NSFileWrapper objects. File
wrappers inside the top-level directory should have preferred names associated with them.
With this brief overview in mind, look again at the following lines of code from the contentsForType:error:
method in Listing 3-6 (page 26). The file package created in this method has two components, a text file and
an image file. (The creation of the image file wrapper is not shown in the snippet.)
if (self.fileWrapper == nil) {
self.fileWrapper = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:nil];
}
NSDictionary *fileWrappers = [self.fileWrapper fileWrappers];
if (([fileWrappers objectForKey:TextFileName] == nil) && (self.text != nil))
{
NSData *textData = [self.text dataUsingEncoding:TextFileEncoding];
NSFileWrapper *textFileWrapper = [[NSFileWrapper alloc]
initRegularFileWithContents:textData];
[textFileWrapper setPreferredFilename:TextFileName];
[self.fileWrapper addFileWrapper:textFileWrapper];
}
The code creates a top-level directory if it doesn’t exist. If a file wrapper doesn’t exist for the text file, it creates
one from the string contents of the text property. It gives this file wrapper a preferred filename and then adds
it to the top-level directory file wrapper.
Formore on NSFileWrapper,seeNSFileWrapper Class Reference ; also see “Exporting theDocumentUTI” (page
22) for the required Info.plist property for document file packages.
Other Method Overrides You Might Make
There are a few other UIDocument overrides that many document-based applications might want to make:
● disableEditing...enableEditing—UIDocument calls the first method when it is unsafe for the user
to make changes to document content, such as when there are updates from iCloud or a revert operation
is underway. You can implement this method to prevent editing during this period. When editing becomes
safe again, UIDocument calls the second method.
Creating a Custom Document Object
Other Method Overrides You Might Make
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
28Note: As an alternative to these overrides, you can observe notifications posted when the
document state changes and, if the new document state is
UIDocumentStateEditingDisabled, prevent editing until the document state changes
again. More more on this topic, see “Monitoring Document-State Changes and Handling
Errors” (page 43).
● savingFileType—This method by default returns the value of the fileType property. If the current
document should be saved under a different file type for any reason, you can override this method to
return the replacement file-type UTI. An example (from Mac OS X) is that when an image is added to an
RTF file, it should be saved as an RTFD file package.
Creating a Custom Document Object
Other Method Overrides You Might Make
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
29A document goes through a typical life cycle. A document-based application is responsible for managing its
progress through that cycle. As you can see from the following list, most of these life-cycle events are initiated
by the user:
● The user first creates a document.
● The user opens an existing document and the application displays it in the document’s view or views.
● The user edits the document.
● A user may ask to put a document in iCloud storage or may request the removal of a document from
iCloud storage.
● During editing, saving, or other actions, errors or conflicts can happen; the application should learn about
these errors and conflicts and either attempt to handle them or inform the user.
● The user closes a selected document.
● The user deletes an existing document.
The following sections discussthe procedures a document-based application must complete for these life-cycle
operations.
Setting the Preferred Storage Location for Document Files
All documents of an application are stored either in the local sandbox or in an iCloud container directory. A
user should not be able to select individual documents for storage in iCloud.
When an application launches for the first time on a device, it should do the following:
●
If iCloud is not configured, ask users if they want to configure it (and, preferably, transfer them to Launch
Settings if they want to configure iCloud).
●
If iCloud is configured but not enabled for the application, ask users if they want to enable iCloud—in
other words, ask if they want all of their documentssaved to iCloud. Store the response as a user preference.
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
30
Managing the Life Cycle of a DocumentBased on this preference, an application writes document files either to the local application sandbox or the
iCloud container directory. (For details, see “Moving Documents to and from iCloud Storage” (page 39).) An
application should expose a switch in the Settings application that enables usersto move documents between
local storage and iCloud storage.
Creating a New Document
A document object (that is, an instance of your custom UIDocument subclass) must have a file URL that locates
the document file either in the local application sandbox or in an iCloud container directory, whichever is the
user’s preference. In addition, a new document can be given a name. The following discusses guidelines and
procedures related to file URLs, document names, and the creation of new documents.
Document Filename Versus Document Name
The UIDocument class assumes a correspondence between the filename of a document and the document
name (also known the display name). By default, UIDocument stores the filename as the value of the
localizedName property. However, an application should not require a user to provide the document filename
or display name when he or she creates a new document.
For your application, you should devise some convention for automatically generating the filenames for your
new documents. Some suggestions are:
● Generate a UUID (universally unique identifier) for each document, optionally with an application-specific
prefix.
● Generate a timestamp (date and time) for each document, optionally with an application-specific prefix.
● Use a sequential numbering system, for example: “Notes 1”, “Notes 2”, and so on.
For the document (display) name, you might initially use the document filename if that makes sense (such as
with “Notes 1”). Or, if the document contains text and the user enters some text in the document, you might
use the first line (or some part of the first line) as the display name. Your application can give users some way
to customize the document name after the document has been created.
Composing the File URL and Saving the Document File
You cannot create a document object without a valid file URL. The file URL has three parts of interest: the path
to the Documents directory in the user’s preferred document location, the document filename, and the
extension of the document file. You can get a URL representing the path to the Documents directory in the
local application sandbox through a method such asthe one in “Document Filename Versus Document Name.”
Managing the Life Cycle of a Document
Creating a New Document
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
31Listing 4-1 Getting a URL to the application’s Documents directory in the local sandbox
-(NSURL*)localDocumentsDirectoryURL {
static NSURL *localDocumentsDirectoryURL = nil;
if (localDocumentsDirectoryURL == nil) {
NSString *documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory,
NSUserDomainMask, YES ) objectAtIndex:0];
localDocumentsDirectoryURL = [NSURL fileURLWithPath:documentsDirectoryPath];
}
return localDocumentsDirectoryURL;
}
The file extension must be one that you specified for the document type (see “Creating and Configuring the
Project” (page 19)). You can declare a global string to represent the extension. For example:
static NSString *FileExtension = @"imageNotes";
The final part of a document’s file URL is the filename component. As “Document Filename Versus Document
Name” (page 31) explains, the application should initially generate the document filename according to some
convention that makes sense for the application. This generated filename can be used as the document name,
or the first line (or part thereof) can be used as the document name. The application can give the user the
option of customizing the document name after the document object has been created.
After you concatenate the base URL, the document filename, and the file extension, you can allocate an instance
of your custom UIDocument subclass and initialize it with the initWithFileURL: method, passing in the
constructed file URL. The final step in creating a new document is to save it to the preferred document storage
location (even though there is no content at this point). Asillustrated by “Setting the Preferred Storage Location
for Document Files ,” you do this by calling the saveToURL:forSaveOperation:completionHandler:
method on the document object.
Listing 4-2 Saving a new document to the file system
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (_createFile) {
[self.document saveToURL:self.document.fileURL
Managing the Life Cycle of a Document
Creating a New Document
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
32forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL
success) {
if (success)
_textView.text = self.document.text;
}];
_createFile = NO;
}
// .....
}
The save-operation parameter of the method call should be UIDocumentSaveForCreating. The final
parameter of the call is a completion hander: a block that is invoked after the save operation concludes. The
parameter of the block tells you whether the operation succeeded. If it did succeed, this code assigns the
document text to the text property of the text view displaying the document content.
Note: If you want to save a new document to the application’s iCloud container directory, it is
recommended that you first save it locally and then call the NSFileManager method
setUbiquitous:itemAtURL:destinationURL:error: to move the document file to iCloud
storage. (This call could be made in the completion handler of the
saveToURL:forSaveOperation:completionHandler: method.) See “Moving Documents to
and from iCloud Storage” (page 39) for further information.
Opening and Closing a Document
Opening a document might at first glance seem to be a fairly easy procedure. Your application scans the
contents of its Documents directory for files having the document’s extension and presents those documents
to the user for selection. However, when iCloud storage is factored in, things get a bit more complicated. Your
application’s documents could be in the Documents directory of the application sandbox or they could be in
the Documents directory of the iCloud container directory.
Discovering an Application’s Documents
To obtain a list of an application’s documents in iCloud storage, run a metadata query. A query is an instance
of the NSMetadataQuery class. After creating a NSMetadataQuery object, you give it a scope and a predicate.
For iCloud storage, the scope should be NSMetadataQueryUbiquitousDocumentsScope. A predicate is an
NSPredicate object that, in this case, constrains a search by filename extension. Before you start running the
Managing the Life Cycle of a Document
Opening and Closing a Document
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
33query, register to observe the NSMetadataQueryDidFinishGatheringNotification and
NSMetadataQueryDidUpdateNotification notifications. The method accepting delivery of these
notifications processes the results of the query.
Listing 4-3 illustrates how you set up and run a metadata query to get the list of application documents in the
iCloud mobile container. The method first tests the user’s preferred storage location for documents (the
documentsInCloud property). If that location isthe mobile container, it runs a metadata query. If the location
is the application sandbox, it iterates through the contents of the application’s Documents directory to get
the names and locations of all local document files.
Listing 4-3 Getting the locations of documents stored locally and in iCloud storage
-(void)viewDidLoad {
[super viewDidLoad];
// set up Add and Edit navigation items here....
if (self.documentsInCloud) {
_query = [[NSMetadataQuery alloc] init];
[_query setSearchScopes:[NSArray
arrayWithObjects:NSMetadataQueryUbiquitousDocumentsScope, nil]];
[_query setPredicate:[NSPredicate predicateWithFormat:@"%K LIKE '*.txt'",
NSMetadataItemFSNameKey]];
NSNotificationCenter* notificationCenter = [NSNotificationCenter
defaultCenter];
[notificationCenter addObserver:self selector:@selector(fileListReceived)
name:NSMetadataQueryDidFinishGatheringNotification object:nil];
[notificationCenter addObserver:self selector:@selector(fileListReceived)
name:NSMetadataQueryDidUpdateNotification object:nil];
[_query startQuery];
} else {
NSArray* localDocuments = [[NSFileManager defaultManager]
contentsOfDirectoryAtPath:
[self.documentsDir path] error:nil];
for (NSString* document in localDocuments) {
[_fileList addObject:[[[FileRepresentation alloc]
initWithFileName:[document lastPathComponent]
url:[NSURL fileURLWithPath:[[self.documentsDir path]
stringByAppendingPathComponent:document]]] autorelease]];
Managing the Life Cycle of a Document
Opening and Closing a Document
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
34}
}
}
In this example, the predicate format is @"%K LIKE '*.txt’", which means to return all filenames (the
NSMetadataItemFSNameKey key) that have a extension of txt, the file extension of this application’s document
files.
After the initial query concludes, and again if there are subsequent updates, the notification method specified
in Listing 4-3 (fileListReceived) isinvoked again. Listing 4-4 showsthis method’simplementation. If query
updates arrive after the user has made a selection, the code also tracks the current selection.
Listing 4-4 Collecting information about documents in iCloud storage
-(void)fileListReceived {
NSString* selectedFileName=nil;
NSInteger newSelectionRow = [self.tableView indexPathForSelectedRow].row;
if (newSelectionRow != NSNotFound) {
selectedFileName = [[_fileList objectAtIndex:newSelectionRow] fileName];
}
[_fileList removeAllObjects];
NSArray* queryResults = [_query results];
for (NSMetadataItem* result in queryResults) {
NSString* fileName = [result valueForAttribute:NSMetadataItemFSNameKey];
if (selectedFileName && [selectedFileName isEqualToString:fileName]) {
newSelectionRow = [_fileList count];
}
[_fileList addObject:[[[FileRepresentation alloc] initWithFileName:fileName
url:[result valueForAttribute:NSMetadataItemURLKey]] autorelease]];
}
[self.tableView reloadData];
if (newSelectionRow != NSNotFound) {
NSIndexPath* selectionPath = [NSIndexPath indexPathForRow:newSelectionRow
inSection:0];
[self.tableView selectRowAtIndexPath:selectionPath animated:NO
scrollPosition:UITableViewScrollPositionNone];
Managing the Life Cycle of a Document
Opening and Closing a Document
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
35}
}
The example application now has an array (_fileList) of custom model objects that encapsulate the name
and file URL of each of the application’s documents. (FileRepresentation is the custom class of those
objects.) The root view controller populates a plain table view with the document names
Note: You should leave metadata queries running only while your application is in the foreground.
You should stop the queries when your application moves to the background.
Downloading Document Files from iCloud
When you run a metadata query to learn about an application’s iCloud documents, the query results are
placeholder items (NSMetadataItem objects) for document files. The items contain metadata about the file,
such as its URL and its modification date. The document file is not in the iCloud container directory.
The actual data for a document is not downloaded until one of the following happens:
● Your application attempts to open or access the file, such as by calling openWithCompletionHandler:.
● Your application callsthe NSFileManagermethod startDownloadingUbiquitousItemAtURL:error:
to download the data explicitly.
Because downloading large document files from iCloud might result in a perceptible delay in displaying the
document data, you should indicate to the user that the download has begun (for example, show “loading”
or “updating”) and that the file is not currently accessible. Remove this indication when the download has
completed.
Note: The NSURLUbiquitousItemIsDownloadedKey associated with the document file’s URL
tells you the document’s current download status. You can retrieve the value of this key using the
getResourceValue:forKey:error: method of the NSURL class. Other keys are also available to
tell you related information about the upload and download status of the file. For more information,
see NSURL Class Reference .
Opening a Document
The sample document-based application lists known documents in a table view. When the user taps a listed
document to open it, UITableView invokes the tableView:didSelectRowAtIndexPath: method of its
delegate. The implementation of this method, shown in Listing 4-5, is typical for the navigation pattern: The
Managing the Life Cycle of a Document
Opening and Closing a Document
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
36root view controller allocates the next view controller in the sequence—in this case, the view controller
presenting document data—and initializes with essential data—in this case, the document’s file URL. Based
on whether the device idiom is iPad or iPhone (or iPhone touch), the root view controller adds the view
controller to the split view or pushes it on the navigation controller’s stack.
Listing 4-5 Responding to a request to open a document
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
*)indexPath
{
[self selectFileAtIndexPath:indexPath create:NO];
}
-(void)selectFileAtIndexPath:(NSIndexPath*)indexPath create:(BOOL)create
{
NSArray* fileList = indexPath.section == 0 ? _localFileList :
_ubiquitousFileList;
DetailViewController* detailViewController = [[DetailViewController alloc]
initWithFileURL:[[fileList objectAtIndex:indexPath.row] url]
createNewFile:create];
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
{
self.splitViewController.viewControllers =
[NSArray arrayWithObjects:self.navigationController,
detailViewController, nil];
}
else {
[self.navigationController pushViewController:detailViewController
animated:YES];
}
[detailViewController release];
}
In itsinitializer method (notshown), the document’s view controller (DetailViewController in the example)
allocates an instance of the UIDocument subclass and initializesit by calling the initWithFileURL: method,
passing in the file URL. It assigns the newly created document object to a document property.
Managing the Life Cycle of a Document
Opening and Closing a Document
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
37The final step in opening a document is to call the openWithCompletionHandler: method on the
UIDocument object; the document’s view controller in our sample application calls this method in
viewWillAppear:, as shown in Listing 4-6. The code checks the document state to verify that the document
is closed before attempting to open it—there’s no need to open an already opened document.
Listing 4-6 Opening a document
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (_createFile) {
[self.document saveToURL:self.document.fileURL
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success) {
_textView.text = self.document.text;
}];
_createFile = NO;
}
else {
if (self.document.documentState & UIDocumentStateClosed) {
[self.document openWithCompletionHandler:nil];
}
}
}
When openWithCompletionHandler: is called, UIDocument reads data from the document file, and the
document object itself creates its model objects from the data. At the conclusion of this sequence of actions,
the completion handler of the openWithCompletionHandler: method is executed. Although the view
controller in the example does not implement the completion block, the completion handler is sometimes
used to assign the document data to the document’s view or views for display. (To recall what
DetailViewController doesinstead to update document views,see Listing 3-4 (page 25) and accompanying
text.)
Closing a Document
To close a document,send a closeWithCompletionHandler: method to the document object. This method
saves the document data, if necessary, and then executes the completion handler in its sole parameter.
Managing the Life Cycle of a Document
Opening and Closing a Document
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
38A good time to close a document is when the document’s view controller is dismissed, such as when the user
taps the back button. Before the view controller’s view disappears, the viewWillDisappear: method is
invoked. Your view controller subclass can override this method in order to call
closeWithCompletionHandler: on the document object, as shown in Listing 4-7.
Listing 4-7 Closing a document
-(void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.document closeWithCompletionHandler:nil];
}
Moving Documents to and from iCloud Storage
As noted in “Setting the Preferred Storage Location for Document Files ” (page 30), an application should give
its users the option of storing all documents in the local file system (the application sandbox) or in iCloud (the
container directory). It stores this option as a user preference and refers to this preference when saving and
opening documents. When the user changes the preference, the application should move all document files
in the application sandbox to iCloud or move all files in the other direction, depending on the nature of the
change.
Getting the Location of the iCloud Container Directory
When you move a document file from local storage to the Documents subdirectory of the iCloud container
directory, its filename is unchanged. The only part of the file-URL path that is different is the part leading up
to Documents. To get that part of the path, you need to call the URLForUbiquityContainerIdentifier:
method of NSFileManager. Most of the time, you pass nil to this method to get your app’s default container
directory. If your app supports multiple containers, you can request containers explicitly by passing in a string
with the corresponding iCloud container identifier—a concatenation of your team ID and an application bundle
ID, separated by a period. These container identifier strings are the same ones you specify in the Identifier field
of your app target’s Summary view in Xcode. It is a good idea to declare a string constant for each of your
app’s container identifiers, as in this example:
static NSString *UbiquityContainerIdentifier =
@"A93A5CM278.com.acme.document.billabong";
The two methods in Listing 4-8 get the iCloud container identifier and append “/Documents” to it.
Managing the Life Cycle of a Document
Moving Documents to and from iCloud Storage
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
39Listing 4-8 Getting the iCloud container directory URL
-(NSURL*)ubiquitousContainerURL {
return [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
}
-(NSURL*)ubiquitousDocumentsDirectoryURL {
return [[self ubiquitousContainerURL] URLByAppendingPathComponent:@"Documents"];
}
Note: For an example of getting the base URL for the application sandbox, see “Composing the File
URL and Saving the Document File” (page 31).
Moving a Document to iCloud Storage
Programmatically, you put a document in iCloud storage by calling the NSFileManager method
setUbiquitous:itemAtURL:destinationURL:error:. This method requiresthe file URL of the document
file in the application sandbox (source URL) and the destination file URL of the document file in the application’s
iCloud container directory. The first parameter takes a Boolean value, which should be YES.
Important: You should not call setUbiquitous:itemAtURL:destinationURL:error: from your
application’s main thread, especially if the document is not closed. Because this method performs a
coordinated write operation on the specified file, calling this method from the main thread can trigger a
deadlock with any file presenter monitoring the file. (In addition, this method executing on the main thread
can take an indeterminate amount of time to complete.) Instead, call the method in a block running in a
dispatch queue other than the main-thread queue. You can always message your main thread after the call
finishes to update the rest of your application’s data structures.
The method in Listing 4-9 illustrates how to move a document file from an application sandbox to iCloud
storage. In the sample application, when the user’s preferred storage location (iCloud or local) changes, this
method is called for every document file in the application sandbox. There are roughly three parts to this
method:
● Compose the source URL and the destination URL.
● On a secondary dispatch queue: Call the setUbiquitous:itemAtURL:destinationURL:error:
method and cache the result, a Boolean value (success) that indicates whether the document file
successfully moved to the iCloud container directory.
Managing the Life Cycle of a Document
Moving Documents to and from iCloud Storage
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
40● On the main dispatch queue: If the callsucceeds, update the document’s model objects and its presentation
of those objects; if the call does not succeed, log the error (or otherwise handle it).
Listing 4-9 Moving a document file to iCloud storage from local storage
- (void)moveFileToiCloud:(FileRepresentation *)fileToMove {
NSURL *sourceURL = fileToMove.url;
NSString *destinationFileName = fileToMove.fileName;
NSURL *destinationURL = [self.documentsDir
URLByAppendingPathComponent:destinationFileName];
dispatch_queue_t q_default;
q_default = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(q_default, ^(void) {
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
NSError *error = nil;
BOOL success = [fileManager setUbiquitous:YES itemAtURL:sourceURL
destinationURL:destinationURL error:&error];
dispatch_queue_t q_main = dispatch_get_main_queue();
dispatch_async(q_main, ^(void) {
if (success) {
FileRepresentation *fileRepresentation = [[FileRepresentation
alloc]
initWithFileName:fileToMove.fileName url:destinationURL];
[_fileList removeObject:fileToMove];
[_fileList addObject:fileRepresentation];
NSLog(@"moved file to cloud: %@", fileRepresentation);
}
if (!success) {
NSLog(@"Couldn't move file to iCloud: %@", fileToMove);
}
});
});
}
Managing the Life Cycle of a Document
Moving Documents to and from iCloud Storage
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
41Removing a Document from iCloud Storage
To move a document file from an iCloud container directory to the Documents directory of the application
sandbox, follow the same procedure described in “Moving a Document to iCloud Storage” (page 40), except
switch the source URL (now the document file in the iCloud container directory) and the destination URL (now
the document file in the application sandbox). In addition, the first parameter of the
setUbiquitous:itemAtURL:destinationURL:error: method should now be NO. Listing 4-10 shows a
method implementing this procedure; it is called for each file in the iCloud container directory, moving it to
the application sandbox.
Listing 4-10 Moving a document file from iCloud storage to local storage
- (void)moveFileToLocal:(FileRepresentation *)fileToMove {
NSURL *sourceURL = fileToMove.url;
NSString *destinationFileName = fileToMove.fileName;
NSURL *destinationURL = [self.documentsDir
URLByAppendingPathComponent:destinationFileName];
dispatch_queue_t q_default;
q_default = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(q_default, ^(void) {
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
NSError *error = nil;
BOOL success = [fileManager setUbiquitous:NO itemAtURL:sourceURL
destinationURL:destinationURL
error:&error];
dispatch_queue_t q_main = dispatch_get_main_queue();
dispatch_async(q_main, ^(void) {
if (success) {
FileRepresentation *fileRepresentation = [[FileRepresentation
alloc]
initWithFileName:fileToMove.fileName url:destinationURL];
[_fileList removeObject:fileToMove];
[_fileList addObject:fileRepresentation];
NSLog(@"moved file to local storage: %@", fileRepresentation);
}
if (!success) {
NSLog(@"Couldn't move file to local storage: %@", fileToMove);
Managing the Life Cycle of a Document
Moving Documents to and from iCloud Storage
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
42}
});
});
}
Monitoring Document-State Changes and Handling Errors
A document can go through different states during its runtime life. A state can tell you whether a document
is experiencing an error, a version conflict, or some other condition that is not normal. UIDocument declares
constants (of type UIDocumentState) to represent document states and sets the documentState property
with one of these constants when a change is a document’sstate occurs. Table 4-1 describesthe state constants.
Table 4-1 UIDocumentState constants
Document state constant What it means
The document is open and is experiencing no conflicts or
other problems.
UIDocumentStateNormal
The document is closed. A document is in this state if
UIDocument cannot open a document, in which case
document properties might not be valid.
UIDocumentStateClosed
UIDocumentStateInConflict There are versions of the document that are in conflict.
UIDocumentStateSavingError An error prevents UIDocument from saving the document.
UIDocumentStateEditingDisabled It is not currently safe to allow users to edit the document.
UIDocument also posts a notification of type UIDocumentStateChangedNotification when a change in
document state occurs. Your application should observe this notification and respond appropriately. The
initializer method of the document’s view controller is a good place to add an observer, as shown in Listing
4-11. The observer in this case is the view controller.
Listing 4-11 Adding an observer of the UIDocumentStateChangedNotification notification
-(id)initWithFileURL:(NSURL*)url createNewFile:(BOOL)createNewFile {
NSString* nibName = [[UIDevice currentDevice] userInterfaceIdiom] ==
UIUserInterfaceIdiomPad ? @"DetailViewController_iPad" :
@"DetailViewController_iPhone";
Managing the Life Cycle of a Document
Monitoring Document-State Changes and Handling Errors
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
43self = [super initWithNibName:nibName bundle:nil];
if (self) {
_document = [[ImageNotesDocument alloc] initWithFileURL:url];
// other code here....
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(documentStateChanged)
name:UIDocumentStateChangedNotification object:_document];
}
return self;
}
Be sure to remove the observer from the notification center in the class’s dealloc method.
When the document’s state changes, UIDocument posts the UIDocumentStateChangedNotification
notification, and the notification center deliversit by invoking the notificationmethod (documentStateChanged
in the example). In Listing 4-12, the observing view controller gets the current state from the documentState
property and evaluates it. If the state is UIDocumentStateEditingDisabled, it hides the keyboard. If there
are conflicts between different versions of the document (UIDocumentStateInConflict), it displays a Show
Conflicts button in the document view’s toolbar. (For detailed information on handling document-version
conflicts, see “Resolving Document Version Conflicts” (page 51).)
Listing 4-12 Evaluating the current document state
-(void)documentStateChanged {
UIDocumentState state = _document.documentState;
[_statusView setDocumentState:state];
if (state & UIDocumentStateEditingDisabled) {
[_textView resignFirstResponder];
}
if (state & UIDocumentStateInConflict) {
[self showConflictButton];
}
else {
[self hideConflictButton];
[self dismissModalViewControllerAnimated:YES];
}
Managing the Life Cycle of a Document
Monitoring Document-State Changes and Handling Errors
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
44}
The notification-handling method also calls a setDocumentState: method implemented by a private view
class. This method, shown in Listing 4-13, changes other items of the document view’s toolbar depending on
the document state.
Listing 4-13 Updating a document’s user interface to reflect its state
-(void)setDocumentState:(UIDocumentState)documentState {
if (documentState & UIDocumentStateSavingError) {
self.unsavedLabel.hidden = NO;
self.circleView.image = [UIImage imageNamed:@"Red"];
}
else {
self.unsavedLabel.hidden = YES;
if (documentState & UIDocumentStateInConflict) {
self.circleView.image = [UIImage imageNamed:@"Yellow"];
}
else {
self.circleView.image = [UIImage imageNamed:@"Green"];
}
}
}
If the document could not be saved (UIDocumentStateSavingError), the view controller changesthe status
indicator to red and displays Unsaved next to it. If there are conflicting document versions, it makes the status
indicator yellow (this is in addition to the Show Conflicts button mentioned earlier). Otherwise, the status
indicator is green.
Deleting a Document
Just as you want to allow users to create a document, you also want to let them delete selected documents.
Deletion of a document requires you to do three things:
● Remove the document file from storage (either from the local sandbox or the iCloud container directory).
● Remove the model objects used to represent the document data in memory.
Managing the Life Cycle of a Document
Deleting a Document
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
45● Remove the document data presented in the document view.
When you delete a document from storage, your code should approximate what UIDocument doesfor reading
and writing operations. It should perform the deletion asynchronously on a background queue, and it should
use file coordination. Listing 4-14 illustrates this procedure. It dispatches a task on a background queue that
creates an NSFileCoordinator object and calls the
coordinateWritingItemAtURL:options:error:byAccessor: method on it. The byAccessor block
of this method calls the NSFileManager method for deleting the file, removeItemAtURL:error:.
Listing 4-14 Deleting a selected document
-(void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
NSMutableArray* fileList = nil;
if (indexPath.section == 0) {
fileList = self.localFileList;
}
else {
fileList = self.ubiquitousFileList;
}
NSURL* fileURL = [[fileList objectAtIndex:indexPath.row] url];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^(void) {
NSFileCoordinator* fileCoordinator = [[NSFileCoordinator alloc]
initWithFilePresenter:nil];
[fileCoordinator coordinateWritingItemAtURL:fileURL
options:NSFileCoordinatorWritingForDeleting
error:nil byAccessor:^(NSURL* writingURL) {
NSFileManager* fileManager = [[NSFileManager alloc] init];
[fileManager removeItemAtURL:writingURL error:nil];
}];
});
[fileList removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[[NSArray alloc] initWithObjects:&indexPath
count:1]
withRowAnimation:UITableViewRowAnimationLeft];
}
Managing the Life Cycle of a Document
Deleting a Document
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
46In this example, the user triggers the invocation of the method when he or she taps the Delete button in a
row while the table view is in editing mode.
Managing the Life Cycle of a Document
Deleting a Document
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
47The saveless-model feature of the UIDocument class ensures that document data is automatically saved at
frequent intervals, relieving users of the need to explicitly save their documents. UIDocument implements
much of the behavior for the saveless model, but a document-based application must play its own part to
make the feature work.
How UIKit Saves Document Data Automatically
The saveless model implemented by the UIKit framework for documents has two main parts: a mechanism for
marking a document as needing to be saved and a variable period for when the framework checks that flag.
Periodically, UIKit callsthe hasUnsavedChanges method of a UIDocument object and evaluatesthe returned
value. If the value is YES, it saves the document data to the document file. The period between checks of the
hasUnsavedChanges value varies according to several factors, including the rate of input by the user.
A document-based application sets the value returned by hasUnsavedChanges indirectly, either by
implementing undo and redo or by tracking changesto the document. Change tracking requiresthe application
to call the updateChangeCount: method, passing in UIDocumentChangeDone (a constant of type
UIDocumentChangeKind). When an application registers an undo action and then sends undo or redo
messages to the document’s undo manager, UIDocument calls updateChangeCount: on its behalf.
Because giving users the ability to undo and redo changes can be a differentiating feature, that approach is
recommended for most applications.
Implementing Undo and Redo
You can implement undo and redo operations in your application by following the procedures and
recommendations in Undo Architecture . Note that UIDocument defines an undoManager property. You can
get the default NSUndoManager object by accessing this property, or you can assign your own NSUndoManager
object to it. The undo manager must be associated with the UIDocument object through the property in order
to enable change tracking and thus automatic saving of document data.
Listing 5-1 illustrates an implementation of undo and redo for a text field.
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
48
Change Tracking and Undo OperationsListing 5-1 Implementing undo and redo for a text field
- (void)textFieldDidEndEditing:(UITextField *)textField {
self.undoButton.enabled = YES;
self.redoButton.enabled = YES;
if (textField.tag == 1) {
[self setLocationText:textField.text];
}
// code for other text fields here....
}
- (void)setLocationText:(NSString *)newText {
NSString *currentText = _document.location;
if (newText != currentText) {
[_document.undoManager registerUndoWithTarget:self
selector:@selector(setLocationText:)
object:currentText];
_document.location = newText;
self.locationField.text = newText;
}
}
- (IBAction)handleUndo:(id)sender {
[_document.undoManager undo];
if (![_document.undoManager canUndo]) self.undoButton.enabled = NO;
}
- (IBAction)handleRedo:(id)sender {
[_document.undoManager redo];
if (![_document.undoManager canRedo]) self.redoButton.enabled = NO;
}
Change Tracking and Undo Operations
Implementing Undo and Redo
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
49Implementing Change Tracking
To implement change tracking instead of implementing undo/redo, call the updateChangeCount: method
on the UIDocument object at the appropriate points in your code. Just as when you register an undo action,
it’s typically at the point where you update the document’s model object with data the user has just entered.
The parameter passed in should be a UIDocumentChangeDone constant.
Listing 5-2 shows how you might call updateChangeCount: from within a UITextViewDelegate method
that is called when a change is made in a text view.
Listing 5-2 Updating the change count of a document
-(void)textViewDidChange:(UITextView *)textView {
_document.documentText = textView.text;
[_document updateChangeCount:UIDocumentChangeDone];
}
Change Tracking and Undo Operations
Implementing Change Tracking
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
50In an iCloud world, when a user has installed a document-based application on multiple devices or desktop
systems, there can be conflicts between different versions of the same document. Recall that an application
updates a document file in the local container directory and those changes are then transmitted—usually
immediately—to iCloud. But what if this transmission is not immediate? For example, you edit a document
using the Mac OS X version of your application, but you’ve also edited the same document using the iPad
version of the application—and you did so while the device wasin Airplane Mode. When you switch off Airplane
Mode, the local change to the document is transferred to iCloud. iCloud notices a conflict and notifies the
application.
Learning About Document Version Conflicts
As “Monitoring Document-State Changes and Handling Errors” (page 43) describes, your application becomes
aware of document-version conflicts by observing the UIDocumentStateChangedNotification notification.
If the documentState property changes to UIDocumentStateInConflict, multiple versions of the same
document exist. The application isresponsible for resolving those conflicts assoon as possible, with or without
the user’s help.
You learn about the conflicting versions of a document through two class methods of the NSFileVersion
class. The currentVersionOfItemAtURL: method returns an NSFileVersion object representing what’s
referred to as the current file ; the current file is chosen by iCloud on some basis as the current “conflict winner”
and is the same across all devices. By calling the unresolvedConflictVersionsOfItemAtURL: method,
you get an array of NSFileVersion objects; these objects are called conflict versions, and each represents
an unresolved version conflict for the file located at the specified URL. NSFileVersion objects can give you
information helpful in resolving conflicts, such as modification dates, localized document names, and localized
names of saving computers.
Strategies for Resolving Document Version Conflicts
Your application can follow one of three strategies for resolving document-version conflicts:
● Merge the changes from the conflicting versions.
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
51
Resolving Document Version Conflicts● Choose one of the document versions based on some pertinent factor, such as the version with the latest
modification date.
● Enable the user to view conflicting versions of a document and select the one to use.
Which strategy is best to use depends a lot upon your document data. If you can merge the contents of different
document versions without introducing contradictory elements, then follow that strategy. Or choose the
document version with the latest modification date if your application doesn’tsuffer any loss of data as a result.
Generally, you should try to resolve the conflict without involving the user, but for some applications that
might not be possible. If an application takes the user-centered approach, it should discreetly inform the user
about the version conflict and expose a button or other control that initiates the resolution procedure. “An
Example: Letting the User Pick the Version” (page 53) examines the code of an application that lets the user
select the document version to use.
How to Tell iOS That a Document Version Conflict Is Resolved
When your application or its users resolve a document version conflict by picking a version of a document,
your application should complete the following steps:
●
If the chosen version is a conflict version, replace the current document file with the conflict-version
document file.
To to this, call the replaceItemAtURL:options:error: method on the NSFileVersion object
representing the version, passing in the document’s current-file URL.
●
If the chosen version is a conflict version, revert the document so that it displays the new data in the
document file
To do this, call the UIDocument method revertToContentsOfURL:completionHandler: on the
document object, passing in the document’s current-file URL.
● Disassociate all conflict versions with the document’s file URL.
To do this, call the NSFileVersion class method removeOtherVersionsOfItemAtURL:error:,
passing in the document’s file URL.
● Mark each conflict version as resolved so that iOS doesn’t raise it again as a conflicting version.
To do this, set the resolved property of each NSFileVersion object representing a conflict version to
YES. This step should always be done last.
● Remove the resolved versions of the document.
For any versions you no longer need, call the removeAndReturnError: method of NSFileVersion to
reclaim the storage for the file. Document revisions remain on the server until you delete them.
Resolving Document Version Conflicts
How to Tell iOS That a Document Version Conflict Is Resolved
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
52An Example: Letting the User Pick the Version
Our sample document-based application is a simple text editor. It would be difficult for such an application to
locate and merge textual differences in conflicting versions of the document, and even if it did, the resulting
document might not be what the user wants. The application could pick the document version with the most
recent modification date, but then again there’s no way to be certain that is the version the user wants. A good
conflict-resolution strategy in this case is to let the user, who is most familiar with the document’s contents,
pick the version she or he wants.
You might recall the code shown in Listing 6-1 from “Monitoring Document-State Changes and Handling
Errors” (page 43). This code shows the method of the document’s view controller that handles the
UIDocumentStateChangedNotification notification posted by UIDocument when there is a change in
document state. If the new document state is UIDocumentStateInConflict, the view controller shows a
Resolve Conflicts button in a custom status view. (It also sets the color of the status indicator to red.)
Listing 6-1 Detecting a conflict in document versions
-(void)documentStateChanged {
UIDocumentState state = _document.documentState;
[_statusView setDocumentState:state];
if (state & UIDocumentStateEditingDisabled) {
[_textView resignFirstResponder];
}
if (state & UIDocumentStateInConflict) {
[self showConflictButton]; // <------ Shows "Resolve Conflicts"
button
}
else {
[self hideConflictButton];
[self dismissModalViewControllerAnimated:YES];
}
}
When the user taps the button, UIKit invokes the method in Listing 6-2. This method displays modally the view
of a custom conflict-resolver view controller.
Resolving Document Version Conflicts
An Example: Letting the User Pick the Version
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
53Listing 6-2 Showing the user interface for resolving document version conflicts
-(void)conflictButtonPushed
{
ConflictResolverViewController* conflictResolver =
[[ConflictResolverViewController alloc]
initWithURL:_document.fileURL delegate:self];
[self presentViewController:conflictResolver animated:YES completion:nil];
[conflictResolver release];
}
The ConflictResolverViewController object creates a page view controller (UIPageViewController
object) that allows user to page between, and examine, the current-file document and each conflict-version
document. In the tool bar of each document view is a Select Version button. If the user taps that button, one
of the two custom delegation methods shown in Listing 6-3 is called, depending on whether the chosen
document is the current-file document or a conflict-version document.
Listing 6-3 Resolving a document version conflict
-(void)conflictResolver:(ConflictResolverViewController *)conflictResolver
didResolveWithFileVersion:(NSFileVersion *)fileVersion {
[self dismissViewControllerAnimated:YES completion:nil];
[fileVersion replaceItemAtURL:_document.fileURL options:0 error:nil];
[NSFileVersion removeOtherVersionsOfItemAtURL:_document.fileURL error:nil];
[_document revertToContentsOfURL:_document.fileURL completionHandler:nil];
NSArray* conflictVersions = [NSFileVersion
unresolvedConflictVersionsOfItemAtURL:_document.fileURL];
for (NSFileVersion* fileVersion in conflictVersions) {
fileVersion.resolved = YES;
}
}
-(void)conflictResolverDidResolveWithCurrentVersion:(ConflictResolverViewController*)conflictResolver
{
[self dismissViewControllerAnimated:YES completion:nil];
[NSFileVersion removeOtherVersionsOfItemAtURL:_document.fileURL error:nil];
NSArray* conflictVersions = [NSFileVersion
unresolvedConflictVersionsOfItemAtURL:_document.fileURL];
Resolving Document Version Conflicts
An Example: Letting the User Pick the Version
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
54for (NSFileVersion* fileVersion in conflictVersions) {
fileVersion.resolved = YES;
}
}
These methods illustrate the steps described in “How to Tell iOS That a Document Version Conflict Is
Resolved” (page 52). If the chosen document is a conflict version, the delegate calls
replaceItemAtURL:options:error: on the passed-in NSFileVersion object to replace the document
file in the iCloud container directory with the chosen document. The delegate then enumerates the array
containing NSFileVersion objectsrepresenting all conflict versions of the document and setsthe resolved
property of each object to YES. It then asks NSFileVersion to remove all other conflict versions of the
document associated with the document’sfileURL and calls revertToContentsOfURL:completionHandler:
to revert the displayed document to the new contents of the document file.
The second delegation method, invoked when the current document file is selected, is much simpler. It sets
the resolved property of all NSFileVersion objects representing conflict versions to YES and removes all
conflict versions associated with the document file URL.
Resolving Document Version Conflicts
An Example: Letting the User Pick the Version
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
55This table describes the changes to Document-Based App Programming Guide for iOS .
Date Notes
2012-09-19 Updated the document's iCloud-related information.
Changed the operator used in the predicate of metadata query searches
to LIKE.
2012-01-09
New document that explains how to create an iOS application whose
documents are integrated with iCloud storage.
2011-10-12
2012-09-19 | © 2012 Apple Inc. All Rights Reserved.
56
Document Revision HistoryApple Inc.
© 2012 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, iPad, iPhone, Mac, Mac
OS, Objective-C, OS X, and Xcode are trademarks
of Apple Inc., registered in the U.S. and other
countries.
iCloud is a service mark of Apple Inc., registered
in the U.S. and other countries.
OpenGL is a registered trademark of Silicon
Graphics, Inc.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
Core Text Programming
GuideContents
Introduction 4
Organization of This Document 4
See Also 4
Core Text Overview 6
OS X Text Technologies 6
Design Goals and Principles 7
Core Text Features and Capabilities 8
System Data Types and Services 8
Core Text Input 8
Characters and Glyphs 9
Core Text Objects 10
Layout Objects 10
Font Objects 13
Common Operations 15
Simple Paragraphs 15
Simple Text Labels 16
Columnar Layout 17
Manual Line Breaking 20
Font Creation and Storage 21
Accessing Font Metrics 25
Creating Related Fonts 27
Document Revision History 29
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
2Figures and Listings
Core Text Overview 6
Figure 1-1 Glyphs of the character A 9
Figure 1-2 Ligatures 9
Figure 1-3 Text layout data flow 10
Figure 1-4 A frame object containing lines and glyph runs 12
Figure 1-5 Creating a font from a font descriptor 14
Common Operations 15
Listing 2-1 Typesetting a simple paragraph 15
Listing 2-2 Typesetting a simple text label 17
Listing 2-3 Performing columnar text layout 18
Listing 2-4 Performing manual line breaking 20
Listing 2-5 Creating a font descriptor from a name and point size 21
Listing 2-6 Creating a font descriptor from a family and traits 21
Listing 2-7 Creating a font from a font descriptor 23
Listing 2-8 Serializing a font 23
Listing 2-9 Creating a font from serialized data 24
Listing 2-10 Calculating line height 26
Listing 2-11 Getting glyphs for characters 26
Listing 2-12 Changing traits of a font 27
Listing 2-13 Converting a font to another family 28
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
3Core Text is an advanced, low-level technology for laying out text and handling fonts. It is designed for high
performance and ease of use. The Core Text API, introduced in OS X v10.5, is accessible from all OS X application
environments. It is also available in iOS 3.2.
The Core Text layout engine is designed specifically to make simple text layout operations easy to do and to
avoid side effects. The Core Text font programming interface is complementary to the Core Text layout engine
and is designed to handle Unicode fonts natively, unifying disparate OS X font facilities into a single
comprehensive programming interface.
This document is intended for developers who need to do text layout and font handling at a low level. If you
can develop your application using higher-level constructs, such as NSTextView, then you should use the
Cocoa text system, introduced in Text System Overview. If, on the other hand, you need to render text directly
into a Core Graphics context, then you should use Core Text.
More information about the position of Core Text among other OS X text technologies is presented in “OS X
Text Technologies” (page 6).
Important: This document has not been updated to address the use of Core Text in iOS 3.2.
Organization of This Document
This document is organized into the following chapters:
“Core Text Overview” (page 6) describes the Core Text system in terms of its design goals and feature set. It
also introducesthe opaque typesthat encapsulate the text layout and font handling capabilities of the system.
“Common Operations” (page 15) presents snippets of code with commentary illustrating typical uses of the
main Core Text opaque types.
See Also
In addition to this document, there are several that cover more specific aspects of Core Text or describe the
software services used by Core Text.
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
4
Introduction● Core Text Reference Collection provides complete reference information for the Core Text layout and font
API.
● CoreTextTest is a sample code project thatshows how to use Core Text in the context of a complete Carbon
application.
● CoreTextArcCocoa is a sample code project that illustrates the use of fonts, lines, and runs in a Core Text
Cocoa application.
● Core Foundation Design Concepts and Core Foundation Framework Reference describe Core Foundation,
a framework that provides abstractions for common data types and fundamental software services used
by Core Text.
The following documents provide entry points to the documentation describing the Cocoa text system.
● Text System Overview gives an introduction to the Cocoa text system.
● Text Layout Programming Guide for Cocoa describes the Cocoa text layout engine.
Introduction
See Also
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
5The Core Text framework is an advanced, low-level technology for laying out text and handling fonts. Designed
for high performance and ease of use, the Core Text layout engine is up to twice as fast as ATSUI (Apple Type
Services for Unicode Imaging). The Core Text layout API is simple, consistent, and tightly integrated with Core
Foundation, Core Graphics, and Cocoa.
The Core Text font API is complementary to the Core Text layout engine. Core Text font technology is designed
to handle Unicode fonts natively, bridging the gap between Carbon and Cocoa font references, and providing
efficient font handling for Core Text layout. Core Text brings the capabilities and coherent design of Cocoa
text and fonts to a broader, lower-level client base.
OS X Text Technologies
The Macintosh operating system has provided sophisticated text handling and typesetting capabilities from
its beginning. In fact, these features sparked the desktop publishing revolution. Core Text is the most modern
text-handling technology on the platform. It is designed specifically for OS X and is written in C, so it can be
called from any language in the system. It is positioned as a core technology to provide consistent,
high-performance textservicesto other frameworksthroughout the system, and the Core Text API is accessible
to applications that need to use it directly. Core Text resides in the Application Services umbrella framework
(ApplicationServices) so that it is callable from both Carbon and Cocoa and has all of the lower-level
services it needs.
Core Text is not meant to replace the Cocoa text system, although it provides the underlying implementation
for many Cocoa text technologies. If you can deal with high-level constructs, such as text views, you can
probably use Cocoa. For this reason, Cocoa developers typically have no need to use Core Text directly. Carbon
developers, on the other hand, will find Core Text faster and easier to use, in many cases, than preexisting OS
X text layout and font APIs.
To decide whether Core Text isthe right OS X text technology for your application, apply the following guidelines:
●
If you can, use Cocoa text. The NSTextView class is the most advanced, full-featured, flexible text view
in OS X. For small amounts of text, use NSTextField.
● To display web content in your application, use Web Kit.
●
If you need to use Carbon only, consider using NSTextView with HICocoaView.
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
6
Core Text Overview●
If you need a lower-level API for drawing any kind of text into a Quartz graphics context (CGContext),
consider using Core Text directly.
Generally speaking, Core Text is for applications that need a low-level text-handling technology correlating
with the Core Graphics framework (Quartz). If you work directly with Quartz and you need to draw some text,
use Core Text. If, for example, you have your own page layout engine—you have some text and you know
where it needs to go in your view—you can use Core Text to generate the glyphs and position them relative
to each other with all the features of fine typesetting,such as kerning, ligatures, line-breaking, and justification.
Design Goals and Principles
Core Text is designed to provide the following benefits:
● A comprehensive, unified set of text-layout and font APIs
● High performance and ease of use
● Tight integration with Cocoa, Core Foundation, and Core Graphics (Quartz)
● Native Unicode handling
● 64-bit application support
● Clean, simple, consistent API design
● Simple interfaces for simple operations
● A flexible interface to layout and glyph data
● A predictable cost structure and rational division of labor
A primary design goal of Core Text layout is to make simple things easy to do. So, for example, if you want to
draw a paragraph of text or a simple text label on the screen, you don’t need much code. A corollary principle
of the Core Text design is that clients are not required to pay for features they don’t use.
The objects defined by Core Text opaque types provide a progression from simplicity to complexity, in terms
of their use and interface. That is, higher-level objects do more for you, and so they are easier to use (although
they may be more complex internally). For example, the highest-level object in Core Text is the framesetter,
which fills a path (defined by a CGPath object representing a rectangle) with text. The framesetter object uses
other Core Text objects, such as typesetter, line, and glyph run objects, to accomplish its work: creating frame
objects, which are lines of glyphs laid out within a shape.
Clients who simply need to lay out a paragraph need only work with the framesetter. Clients who need to
intervene in the text layout process at a lower level can deal with lower level objects, such as line objects. Line
objects can draw themselves individually or be used to obtain glyph information. With Core Text you use the
highest-level object you can to get your job done.
Core Text Overview
Design Goals and Principles
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
7Core Text Features and Capabilities
Core Text performs text layout and font access. The text layout engine generates glyphs from characters and
positionsthe glyphsinto glyph runs, lines, and multiline frames. It also provides glyph- and layout-related data,
such as glyph positioning and measurement of lines and frames. The API handles character attributes and
paragraph styles, including various types of tab styles and positioning.
The Core Text font API bringsto Carbon developersthe same capabilities enjoyed by Cocoa developersthrough
NSFont and NSFontDescriptor. The API provides font viewing and selecting. It provides font references,
font descriptors (objects that encapsulate font data sufficient to instantiate a font reference), and easy access
to font data. It also provides support for multiple master fonts, font variations, font cascading, and font linking.
The Core Text font API is designed to be very complete, so that you don’t have to go to different layers to do
what you need to do.
System Data Types and Services
Core Text uses system data types and services wherever possible, and you use the same conventions that
pertain to the other core frameworks in OS X. So, for example, Core Text uses Core Foundation objects for
many input and output parameters, enabling them to be retained, released, and stored in Core Foundation
collection classes. Other objects handled by Core Text are provided by the Core Graphics framework, for
example, CGPath objects. Moreover, because many Core Foundation objects are toll-free bridged with Cocoa
Foundation objects, you can usually use Foundation objects in place of Core Foundation objects passed into
Core Text functions. Use of these standard types and toll-free bridging ensure that you don’t have to perform
expensive type conversions to get data into and out of Core Text.
Core Text is built to work directly with Core Graphics, also known as Quartz, which is the high-speed graphics
rendering engine that handles two-dimensional imaging at the lowest level in OS X. Quartz is the only way to
get glyphs drawn at a fundamental level, and, because Core Text provides all data in a form directly usable by
Quartz, the result is high-performance text rendering.
Core Text Input
The input type most basic to Core Text is the Core Foundation attributed string, represented by
CFAttributedStringRef or its Cocoa counterpart, NSAttributedString, which are toll-free bridged. The
attributes are key-value pairs that define style characteristics of the characters in the string, which are grouped
in rangesthatshare the same attributes. Examples of text attributes are font and color. The attributesthemselves
are passed into attributed strings, and retrieved from them, using CFDictionary objects. (Though
CFDictionaryRef and NSDictionary are also toll-free bridged, the individual attribute objects stored in
the dictionary may not be.) The typesetting mechanism in Core Text uses the information in the attributed
string to perform character-to-glyph conversion.
Core Text Overview
Core Text Features and Capabilities
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
8Characters and Glyphs
One of the most important capabilities of fine typesetting is character-to-glyph conversion. It is important to
distinguish between characters and glyphsin discussing a text layout engine. Characters are essentially numbers
representing code points in a character set or encoding scheme, such as Unicode, the character set used for
all text in OS X. The Unicode standard provides a unique number for every character in every modern written
language in the world, independent of the platform, program, and programming language being used.
A glyph is a graphic shape used to depict a character. Glyphs are also represented by numeric codes, called
glyph codes, that are indexesinto a particular font. Glyphs are selected during composition and layout processing
by the character-to-glyph conversion process. There are any number of glyphs that correspond to a particular
character. For example, the character“uppercase A” has different glyphsfor different typefaces(such as Helvetica
and Times) and type styles (such as bold and italic). Figure 1-1 shows various glyphs, all of which represent an
“uppercase A.“
Figure 1-1 Glyphs of the character A
Moreover, the correspondence between characters and glyphs is not one to one, and the context within which
a character appears can affect the glyph chosen to represent it. For example, in many fonts an “f” and “l”
appearing side-by-side in a character string are replaced by a ligature, which is a single glyph depicting the
letters joined together. Figure 1-2 shows two examples of individual characters and the single-glyph ligatures
often used when they are adjacent. Character-to-glyph conversion is a complex and difficult task that Core
Text performs quickly and efficiently.
Figure 1-2 Ligatures
+ =
+ =
Core Text Overview
Core Text Features and Capabilities
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
9Core Text Objects
Core Text objects are based on the corresponding opaque types defined by the framework. In the sections
that follow, you learn how the primary Core Text objects interact to accomplish various client tasks.
Layout Objects
Layout objects make up the Core Text layout engine. This section discusses the primary layout objects:
framesetter, frame, typesetter, line, and glyph run objects. In addition this section briefly discusses the other
Core Text layout objects: paragraph styles, text tabs, and glyph info objects.
Framesetters and Frames
The framesetter is the highest-level object in the Core Text layout engine, represented by the CTFramesetter
opaque type. A framesetter generates text frames by filling a path with text. That is, CTFramesetter is an object
factory for CTFrame objects that are ready to draw.
The framesetter takes an attributed string object (CFAttributedString) and a shape descriptor object (CGPath)
and calls into the typesetter to create line objects that fill that shape. The output is a frame object containing
an array of lines. This array of lines is a paragraph, a multiline layout. The frame can draw itself directly into a
graphic context. You can also retrieve the lines to manipulate before drawing. For example, you might adjust
their positioning. Figure 1-3 shows the data flow among objects performing text layout.
Figure 1-3 Text layout data flow
CFAttributedString CTFramesetter
CTTypesetter
CTFrame
CGPath
The framesetter applies paragraph styles to the frame text as it is laid out. Paragraph styles are represented in
Core Text by objects storing attributes that affect paragraph layout. Among these attributes are alignment,
tab stops, writing direction, line-breaking mode, and indentation settings.
Core Text Overview
Core Text Objects
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
10It’s advantageousto use the framesetter to perform the common operation of typesetting a multiline paragraph
because it handles all of the details of producing frames, instantiating other objects, such as the typesetter, as
needed. The CTFramesetter opaque type provides functions to create a framesetter with an attributed string,
to create frame objects, and to return itstypesetter. As with all Core Text objects, CTFramesetter can also return
its Core Foundation type identifier.
Typesetters
A typesetter performsthe fundamental text layout operations of character-to-glyph conversion and positioning
of those glyphs into lines. That is, it determines which glyphs to use and where to place them relative to each
other, producing line objects. Typesetters are represented by the CTTypesetter opaque type.
The typesetter also suggests line breaks. It finds how many glyphs can fit within a single line within a given
space. It then determines the length of the line by using word breaks, word wrapping, or finer-grained cluster
breaks. Simple word wrapping is the default method of creating line breaks.
The framesetter instantiates a typesetter and uses it to create the line objects used to fill a frame. You can also
use a typesetter directly, as described in “Manual Line Breaking” (page 20).
Lines and Glyph Runs
A line object represents a line of text and is represented in Core Text by the CTLine opaque type. A CTLine
object contains an array of glyph runs. Line objects are created by the typesetter during a framesetting operation
and, like frames, can draw themselves directly into a graphics context. Line objects hold the glyphs that are
the result of the text layout process, created from text and style information.
A line corresponds to a range of characters. It could be miles long or, more often, one of a series of lines
contained within a paragraph. The paragraph is represented in Core Text by a CTFrame object, which contains
the paragraph’s line objects. Accordingly, you can retrieve line objects from their frame object.
A line object contains glyph-run objects, represented by the CTRun opaque type. A glyph run is a set of
consecutive glyphs sharing the same attributes and direction. The typesetter creates glyph runs as it produces
lines from character strings, attributes, and font objects. That is, a line is constructed of one or more glyphs
Core Text Overview
Core Text Objects
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
11runs. Glyph runs can draw themselves into a graphic context, if desired, although most clients have no need
to interact directly with glyph runs. Figure 1-4 shows the conceptual hierarchy of a frame object containing
line objects that, in turn, contain glyph-run objects.
Figure 1-4 A frame object containing lines and glyph runs
CTRun
CTLine
CTFrame
a glyph run
CTLine has a convenience method for creating a freestanding line independent of a frame,
CTLineCreateWithAttributedString. You can use this method to create a line object directly from an
attributed string without needing to create and manage a typesetter. Without a typesetter, however, there’s
no way to calculate line breaks, so this method is meant for a single line only (for example, creating a text
label).
After you have a line object, you can do a number of things with it. For example, you can have the line create
a justified or truncated copy of itself, and you can ask the line for pen offsets for various degrees of flushness.
You can use these pen offsets to draw the line with left, right, or centered alignments. You can also ask the
line for measurements, such as its image bounds and typographic bounds. Image bounds represent the
rectangle tightly enclosing the graphic shapes of the glyphs actually appearing in the line. Typographic bounds
include the height of the ascenders in the font and the depth of its descenders, regardless of whether those
features appear in the glyphs in a given line.
Like a frame object, a line object is ready to draw. You simply set the text position in a Core Graphics context
and have the line draw itself. Core Text uses the same placement strategy as Quartz, setting the origin of the
text on the text baseline.
In Quartz, you specify the location of text in user-space coordinates. The text matrix specifies the transform
from text space to user space. The text position is stored in the tx and ty variables of the text matrix. When
you first create a graphics context, it initializesthe text matrix to the identity matrix; thustext-space coordinates
are initially the same as user-space coordinates. Quartz conceptually concatenates the text matrix with the
current transformation matrix and other parametersfrom the graphicsstate to produce the final text-rendering
matrix, that is, the matrix actually used to draw the text on the page.
Core Text Overview
Core Text Objects
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
12Other Layout Objects
In addition to the framesetter, frame, typesetter, and line objects, Core Text provides other objects to complete
the text layout process: paragraph style, text tab, and glyph info objects.
Paragraph style objects encapsulate paragraph or ruler attributes in an attributed string and are represented
by the CTParagraphStyle opaque type. A paragraph style object is a complex attribute value in an attributed
string, storing a number of subattributes that affect paragraph layout for the characters of the string. Among
these subattributes are alignment, tab stops, writing direction, line-breaking mode, and indentation settings.
The CTTextTab opaque type represents a tab stop in a paragraph style, storing an alignment type and location.
The CTGlyphInfo opaque type enables you to override a font's specified mapping from Unicode to the glyph
ID.
Font Objects
Font objects are those Core Text objects dealing directly with fonts: the font reference itself, font descriptor
objects, and font collection objects.
Fonts
Fonts provide assistance in laying out glyphs relative to one another and are used to establish the current font
when drawing in a graphics context. The Core Text font opaque type (CTFont) is a specific font instance that
encapsulates a lot of information. Its reference type, CTFontRef, is toll-free bridged with NSFont. When you
create a CTFont object, you typically specify (or use a default) pointsize and transformation matrix, which gives
the font instance specific characteristics. You can then query the font object for many kinds of information
about the font at that particular point size, such as character-to-glyph mapping, encodings, font metric data,
and glyph data, among other things. Font metrics are parameters such as ascent, descent, leading, cap height,
x-height, and so on. Glyph data includes parameters such as bounding rectangles and glyph advances.
There are many ways to create font references. The preferred method is from a font descriptor using
CTFontCreateWithFontDescriptor. You can also use a number of conversion APIs, depending on what
you have to start with. For example, you can use the PostScript name of the typeface (CTFontCreateWithName),
an ATS font reference (CTFontCreateWithPlatformFont), a Core Graphics font reference
(CTFontCreateWithGraphicsFont), or a QuickDraw font reference
(CTFontCreateWithQuickdrawInstance). There’s also CTFontCreateUIFontForLanguage, which creates
a reference for the user-interface font for the application you’re using in the localization you’re using.
Core Text font references provide a sophisticated, automatic font-substitution mechanism called font cascading.
This mechanism takes font traits into account, so it does a better job than previous schemes of picking an
appropriate font to substitute for a missing font. Font cascading is based on cascade lists, which are arrays of
ordered font descriptors. There is a system default cascade list (which is polymorphic, based on the user's
language setting and current font) and a font cascade list that is specified at font creation time. Using the
Core Text Overview
Core Text Objects
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
13information in the font descriptors, the cascading mechanism can match fonts according to style as well as
matching characters. The CTFontCreateForString function uses cascade lists to pick an appropriate font
to encode a given string. You specify and retrieve font cascade lists using the kCTFontCascadeListAttribute
property.
Font Descriptors
Font descriptors, represented by the CTFontDescriptor opaque type, provide a mechanism to describe a font
completely with a dictionary of attributes. CTFontDescriptorRef istoll-free bridged to NSFontDescriptor.
The attributes are properties such as PostScript name, family, and style, and traits such as bold, italic, and
monospace. The font descriptor can then be used to create or modify a CTFont object. Font descriptors can
be serialized and stored in a document to provide persistence for fonts. Figure 1-5 illustrates the font system
using a font descriptor to create a specific font instance.
Figure 1-5 Creating a font from a font descriptor
Font System
CTFontDescriptor CTFont
A font descriptor can also be considered as a query into the font system. You can create a font descriptor with
an incomplete specification, that is, with one or just a few values in the attribute dictionary, and the system
will choose the most appropriate font from those available. The system can also give you a complete list of
font descriptors matching your query via CTFontDescriptorCreateMatchingFontDescriptors.
Font Collections
Font collections are unions of font descriptors, that is, groups of font descriptors taken as a single object. A
font collection is represented by the CTFontCollection opaque type. Font collections provide the capabilities
of font enumeration, accessto global and custom font collections, and accessto the font descriptors comprising
the collection. You can, for example, create a font collection of all the fonts available in the system by calling
CTFontCollectionCreateFromAvailableFonts, and you can use the collection to obtain an array of all
of the member font descriptors. There is also a function that takes a callback parameter used to sort the returned
array of font descriptors.
Core Text Overview
Core Text Objects
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
14This chapter describes some common text-layout and font-handling operations and shows, through portions
of sample code, how they can be accomplished using Core Text. In addition to the code fragments in this
chapter, see the following sample code applications that use Core Text:
● CoreTextTest shows how to use Core Text to typeset blocks of text with varying attributes in the context
of a complete Carbon application.
● CoreTextArcCocoa illustrates the use of Core Text fonts, lines, and runs in a Cocoa application that sets
type along an arched path.
Simple Paragraphs
One of the most common operations in typesetting is laying out a multiline paragraph within an arbitrarily
sized rectangular area. Core Text makes this operation easy, requiring only a few lines of Core Text–specific
code. To lay out the paragraph, you need a graphics context to draw into, a rectangular path to provide the
area where the text is laid out, and an attributed string. Most of the code in this example is required to create
and initialize the context, path, and string. After that is done, Core Text requires only three lines of code to do
the layout.
Listing 2-1 uses Cocoa to simplify initialization of the graphics context. To see how that operation is done in
Carbon, see the CoreTextTest sample code or “Graphics Contexts“ in the Quartz 2D Programming Guide .
Listing 2-1 Typesetting a simple paragraph
// Initialize a graphics context and set the text matrix to a known value.
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext]
graphicsPort];
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// Initialize a rectangular path.
CGMutablePathRef path = CGPathCreateMutable();
CGRect bounds = CGRectMake(10.0, 10.0, 200.0, 200.0);
CGPathAddRect(path, NULL, bounds);
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
15
Common Operations// Initialize an attributed string.
CFStringRef string = CFSTR("We hold this truth to be self-evident, that
everyone is created equal.");
CFMutableAttributedStringRef attrString =
CFAttributedStringCreateMutable(kCFAllocatorDefault, 0);
CFAttributedStringReplaceString (attrString,
CFRangeMake(0, 0), string);
// Create a color and add it as an attribute to the string.
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[] = { 1.0, 0.0, 0.0, 0.8 };
CGColorRef red = CGColorCreate(rgbColorSpace, components);
CGColorSpaceRelease(rgbColorSpace);
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 50),
kCTForegroundColorAttributeName, red);
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter =
CTFramesetterCreateWithAttributedString(attrString);
CFRelease(attrString);
// Create the frame and draw it into the graphics context
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
CFRangeMake(0, 0), path, NULL);
CFRelease(framesetter);
CTFrameDraw(frame, context);
CFRelease(frame);
Simple Text Labels
Another very common typesetting operation is drawing a single line of text to use as a label for a user-interface
element. In Core Text thisrequires only two lines of code, one to create the line object with an attributed string
and another to draw the line into a graphic context.
Common Operations
Simple Text Labels
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
16Listing 2-2 omits initialization of the plain text string, font, and graphics context, but it shows how to create
an attributes dictionary and use it to create the attributed string. To see how to create a Core Text font, see
“Font Creation and Storage” (page 21).
Listing 2-2 Typesetting a simple text label
CFStringRef string; CTFontRef font; CGContextRef context;
// Initialize string, font, and context
CFStringRef keys[] = { kCTFontAttributeName };
CFTypeRef values[] = { font };
CFDictionaryRef attributes =
CFDictionaryCreate(kCFAllocatorDefault, (const void**)&keys,
(const void**)&values, sizeof(keys) / sizeof(keys[0]),
&kCFTypeDictionaryCallBacks,
&kCFTypeDictionaryValueCallbacks);
CFAttributedStringRef attrString =
CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
CFRelease(string);
CFRelease(attributes);
CTLineRef line = CTLineCreateWithAttributedString(attrString);
// Set text position and draw the line into the graphics context
CGContextSetTextPosition(context, 10.0, 10.0);
CTLineDraw(line, context);
CFRelease(line);
Columnar Layout
Laying out text in multiple columns is another common typesetting operation. Strictly speaking, Core Text
itself only performs the layout of one column at a time and does not calculate the column sizes or locations.
You do those operations before calling Core Text to lay out the text within the rectangular path area you’ve
calculated. In this sample, Core Text, in addition to laying out the text in each column, also provides the
subrange within the text string for each column.
Common Operations
Columnar Layout
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
17Listing 2-3 mixes Cocoa method calls in Objective-C with function calls into Carbon frameworks and Core Text.
It includes an implementation of the drawRect: method of NSView, which calls the local createColumns
function, defined first in this listing. This code resides in an NSView subclass in a Cocoa document-based
application. The NSView subclassincludes an attributedString accessor method, which is notshown here
but is called in this listing to return the attributed string to be laid out.
Listing 2-3 Performing columnar text layout
- (CFArrayRef)createColumns {
CGRect bounds = CGRectMake(0, 0, NSWidth([self bounds]),
NSHeight([self bounds]));
int column;
CGRect* columnRects = (CGRect*)calloc(_columnCount, sizeof(*columnRects));
// Start by setting the first column to cover the entire view.
columnRects[0] = bounds;
// Divide the columns equally across the frame's width.
CGFloat columnWidth = CGRectGetWidth(bounds) / _columnCount;
for (column = 0; column < _columnCount - 1; column++) {
CGRectDivide(columnRects[column], &columnRects[column],
&columnRects[column + 1], columnWidth, CGRectMinXEdge);
}
// Inset all columns by a few pixels of margin.
for (column = 0; column < _columnCount; column++) {
columnRects[column] = CGRectInset(columnRects[column], 10.0, 10.0);
}
// Create an array of layout paths, one for each column.
CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault,
_columnCount, &kCFTypeArrayCallBacks);
for (column = 0; column < _columnCount; column++) {
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, columnRects[column]);
CFArrayInsertValueAtIndex(array, column, path);
CFRelease(path);
Common Operations
Columnar Layout
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
18}
free(columnRects);
return array;
}
- (void)drawRect:(NSRect)rect {
// Draw a white background.
[[NSColor whiteColor] set];
[NSBezierPath fillRect:[self bounds]];
// Initialize the text matrix to a known value.
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext]
graphicsPort];
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CTFramesetterRef framesetter =
CTFramesetterCreateWithAttributedString(
(CFAttributedStringRef)[self attributedString]);
CFArrayRef columnPaths = [self createColumns];
CFIndex pathCount = CFArrayGetCount(columnPaths);
CFIndex startIndex = 0;
int column;
for (column = 0; column < pathCount; column++) {
CGPathRef path = (CGPathRef)CFArrayGetValueAtIndex(columnPaths, column);
// Create a frame for this column and draw it.
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
CFRangeMake(startIndex, 0), path, NULL);
CTFrameDraw(frame, context);
// Start the next frame at the first character not visible in this frame.
CFRange frameRange = CTFrameGetVisibleStringRange(frame);
startIndex += frameRange.length;
Common Operations
Columnar Layout
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
19CFRelease(frame);
}
CFRelease(columnPaths);
}
Manual Line Breaking
You usually don't need to do manual line breaking unless you have a special hyphenation process or a similar
requirement. A framesetter performs line breaking automatically. Listing 2-4 shows how to create a typesetter,
an object used by the framesetter, and use it directly to find appropriate line breaks and create a typeset line
manually. This sample also shows how to center the line before drawing.
Listing 2-4 Performing manual line breaking
double width; CGContextRef context; CGPoint textPosition; CFAttributedStringRef
attrString;
// Initialize those variables.
// Create a typesetter using the attributed string.
CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString(attrString);
// Find a break for line from the beginning of the string to the given width.
CFIndex start = 0;
CFIndex count = CTTypesetterSuggestLineBreak(typesetter, start, width);
// Use the returned character count (to the break) to create the line.
CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(start, count));
// Get the offset needed to center the line.
float flush = 0.5; // centered
double penOffset = CTLineGetPenOffsetForFlush(line, flush, width);
// Move the given text drawing position by the calculated offset and draw the line.
CGContextSetTextPosition(context, textPosition.x + penOffset, textPosition.y);
CTLineDraw(line, context);
Common Operations
Manual Line Breaking
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
20// Move the index beyond the line break.
start += count;
Font Creation and Storage
The example function in Listing 2-5 creates a font descriptor from a PostScript font name and a float specifying
the point size.
Listing 2-5 Creating a font descriptor from a name and point size
CTFontDescriptorRef CreateFontDescriptorFromName(CFStringRef iPostScriptName,
CGFloat iSize)
{
assert(iPostScriptName != NULL);
return CTFontDescriptorCreateWithNameAndSize(iPostScriptName, iSize);
}
The example function in Listing 2-6 creates a font descriptor from a font family name and font traits.
Listing 2-6 Creating a font descriptor from a family and traits
CTFontDescriptorRef CreateFontDescriptorFromFamilyAndTraits(CFStringRef iFamilyName,
CTFontSymbolicTraits iTraits, CGFloat iSize)
{
CTFontDescriptorRef descriptor = NULL;
CFMutableDictionaryRef attributes;
assert(iFamilyName != NULL);
// Create a mutable dictionary to hold our attributes.
attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
check(attributes != NULL);
if (attributes != NULL) {
CFMutableDictionaryRef traits;
Common Operations
Font Creation and Storage
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
21CFNumberRef symTraits;
// Add a family name to our attributes.
CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, iFamilyName);
// Create the traits dictionary.
symTraits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
&iTraits);
check(symTraits != NULL);
if (symTraits != NULL) {
// Create a dictionary to hold our traits values.
traits = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
check(traits != NULL);
if (traits != NULL) {
// Add the symbolic traits value to the traits dictionary.
CFDictionaryAddValue(traits, kCTFontSymbolicTrait, symTraits);
// Add the traits attribute to our attributes.
CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits);
CFRelease(traits);
}
CFRelease(symTraits);
}
// Create the font descriptor with our attributes and input size.
descriptor = CTFontDescriptorCreateWithAttributes(attributes);
check(descriptor != NULL);
CFRelease(attributes);
}
// Return our font descriptor.
return descriptor;
Common Operations
Font Creation and Storage
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
22}
The example function in Listing 2-7 creates a font from a provided font descriptor. It calls
CTFontCreateWithFontDescriptor, passing NULL for the matrix parameter to specify the default (identity)
matrix.
Listing 2-7 Creating a font from a font descriptor
CTFontRef CreateFont(CTFontDescriptorRef iFontDescriptor, CGFloat iSize)
{
check(iFontDescriptor != NULL);
// Create the font from the font descriptor and input size. Pass
// NULL for the matrix parameter to use the default matrix (identity).
return CTFontCreateWithFontDescriptor(iFontDescriptor, iSize, NULL);
}
The example function in Listing 2-8 creates XML data to serialize a font for embedding in a document.
Alternatively, and preferably, NSArchiver could be used. This is just one way to accomplish this task, but it
preserves all data from the font needed to re-create the exact font at a later time.
Listing 2-8 Serializing a font
CFDataRef CreateFlattenedFontData(CTFontRef iFont)
{
CFDataRef result = NULL;
CTFontDescriptorRef descriptor;
CFDictionaryRef attributes;
check(iFont != NULL);
// Get the font descriptor for the font.
descriptor = CTFontCopyFontDescriptor(iFont);
check(descriptor != NULL);
if (descriptor != NULL) {
Common Operations
Font Creation and Storage
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
23// Get the font attributes from the descriptor. This should be enough
// information to recreate the descriptor and the font later.
attributes = CTFontDescriptorCopyAttributes(descriptor);
check(attributes != NULL);
if (attributes != NULL) {
// If attributes are a valid property list, directly flatten
// the property list. Otherwise we may need to analyze the attributes
// and remove or manually convert them to serializable forms.
// This is left as an exercise for the reader.
if (CFPropertyListIsValid(attributes, kCFPropertyListXMLFormat_v1_0))
{
result = CFPropertyListCreateXMLData(kCFAllocatorDefault,
attributes);
check(result != NULL);
}
}
}
return result;
}
The example function in Listing 2-9 creates a font reference from flattened XML data. Itshows how to unflatten
font attributes and create a font with those attributes.
Listing 2-9 Creating a font from serialized data
CTFontRef CreateFontFromFlattenedFontData(CFDataRef iData)
{
CTFontRef font = NULL;
CFDictionaryRef attributes;
CTFontDescriptorRef descriptor;
check(iData != NULL);
// Create our font attributes from the property list. We will create
// an immutable object for simplicity, but if you needed to massage
Common Operations
Font Creation and Storage
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
24// the attributes or convert certain attributes from their serializable
// form to the Core Text usable form, you could do it here.
attributes =
(CFDictionaryRef)CFPropertyListCreateFromXMLData(kCFAllocatorDefault,
iData, kCFPropertyListImmutable, NULL);
check(attributes != NULL);
if (attributes != NULL) {
// Create the font descriptor from the attributes.
descriptor = CTFontDescriptorCreateWithAttributes(attributes);
check(descriptor != NULL);
if (descriptor != NULL) {
// Create the font from the font descriptor. We will use
// 0.0 and NULL for the size and matrix parameters. This
// causes the font to be created with the size and/or matrix
// that exist in the descriptor, if present. Otherwise default
// values are used.
font = CTFontCreateWithFontDescriptor(descriptor, 0.0, NULL);
check(font != NULL);
}
}
return font;
}
Accessing Font Metrics
For every font, glyph designers provide a set of measurements, called metrics, which describe the spacing
around each glyph in the font. The typesetter uses these metrics to determine glyph placement. Font metrics
are parameters such as ascent, descent, leading, cap height, x-height, and so on.
The sample functionsin thissection illustrate how to query a font for itsfont metric data. The example function
in Listing 2-10 shows how to use line metrics accessors to calculate the line height for a font. In most cases
you should not need to do this yourself. If you have a CTLineRef object for a line of text , you could call
CTLineGetTypographicBounds to get the line metrics for the line.
Common Operations
Accessing Font Metrics
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
25Listing 2-10 Calculating line height
CGFloat GetLineHeightForFont(CTFontRef iFont)
{
CGFloat lineHeight = 0.0;
check(iFont != NULL);
// Get the ascent from the font, already scaled for the font's size
lineHeight += CTFontGetAscent(iFont);
// Get the descent from the font, already scaled for the font's size
lineHeight += CTFontGetDescent(iFont);
// Get the leading from the font, already scaled for the font's size
lineHeight += CTFontGetLeading(iFont);
return lineHeight;
}
The example function in Listing 2-11 demonstrates how to get glyphsfor the charactersin a string with a single
font. Most of the time you should just use a CTLine object to get this information because one font may not
encode the entire string. In addition, simple character-to-glyph mapping will not get the correct appearance
for complex scripts. Thissimple glyph mapping may be appropriate if you are trying to display specific Unicode
characters for a font.
Listing 2-11 Getting glyphs for characters
void GetGlyphsForCharacters(CTFontRef iFont, CFStringRef iString)
{
UniChar *characters;
CGGlyph *glyphs;
CFIndex count;
assert(iFont != NULL && iString != NULL);
Common Operations
Accessing Font Metrics
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
26// Get our string length.
count = CFStringGetLength(iString);
// Allocate our buffers for characters and glyphs.
characters = (UniChar *)malloc(sizeof(UniChar) * count);
assert(characters != NULL);
glyphs = (CGGlyph *)malloc(sizeof(CGGlyph) * count);
assert(glyphs != NULL);
// Get the characters from the string.
CFStringGetCharacters(iString, CFRangeMake(0, count), characters);
// Get the glyphs for the characters.
CTFontGetGlyphsForCharacters(iFont, characters, glyphs, count);
// Do something with the glyphs here, if a character is unmapped
// Free our buffers
free(characters);
free(glyphs);
}
Creating Related Fonts
The example functions in this section show how to query fonts for their attributes. The example function in
Listing 2-12 makes a font bold or unbold based on the value of the Boolean parameter passed with the function
call. If the current font family does not have the requested style, the function returns NULL.
Listing 2-12 Changing traits of a font
CTFontRef CreateBoldFont(CTFontRef iFont, Boolean iMakeBold)
{
CTFontSymbolicTraits desiredTrait = 0;
Common Operations
Creating Related Fonts
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
27CTFontSymbolicTraits traitMask;
// If we are trying to make the font bold, set the desired trait
// to be bold.
if (iMakeBold)
desiredTrait = kCTFontBoldTrait;
// Mask off the bold trait to indicate that it is the only trait
// desired to be modified. As CTFontSymbolicTraits is a bit field,
// we could choose to change multiple traits if we desired.
traitMask = kCTFontBoldTrait;
// Create a copy of the original font with the masked trait set to the
// desired value. If the font family does not have the appropriate style,
// this will return NULL.
return CTFontCreateCopyWithSymbolicTraits(iFont, 0.0, NULL, desiredTrait,
traitMask);
}
The example function in Listing 2-13 converts a given font to a similar font in another font family, preserving
traits if possible. It may return NULL.
Listing 2-13 Converting a font to another family
CTFontRef CreateFontConvertedToFamily(CTFontRef iFont, CFStringRef iFamily)
{
// Create a copy of the original font with the new family. This call
// attempts to preserve traits, and may return NULL if that is not possible.
// Pass in 0.0 and NULL for size and matrix to preserve the values from
// the original font.
return CTFontCreateCopyWithFamily(iFont, 0.0, NULL, iFamily);
}
Common Operations
Creating Related Fonts
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
28This table describes the changes to Core Text Programming Guide .
Date Notes
2010-03-03 Added this document for iOS 3.2.
2010-01-20 Removed links to deprecated documents and sample code.
Revised Figure 1-3 "Text layout data flow" to show that a CGPath object
is provided in the creation of a CTFrame object.
2008-06-09
2008-02-08 Fixed bad link to sample code in Introduction.
2007-12-11 Made minor editorial corrections.
New document that explains how to perform text layout and font-related
operations using the Core Text programming interfaces.
2007-07-16
2010-03-03 | © 2010 Apple Inc. All Rights Reserved.
29
Document Revision HistoryApple Inc.
© 2010 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Carbon, Cocoa, Mac,
Macintosh, Objective-C, OS X, Quartz, and
QuickDraw are trademarks of Apple Inc.,
registered in the U.S. and other countries.
Helvetica and Times are registered trademarks of
Heidelberger Druckmaschinen AG, available from
Linotype Library GmbH.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
OpenCL Programming
Guide for MacContents
About Open CL for OS X 7
At a Glance 7
Prerequisites 8
See Also 8
Developing OpenCL Programs Using Xcode 9
Concepts 9
Essential Development Tasks 11
Hello World! 12
Creating An Application That Uses OpenCL In Xcode 12
Compiling From the Command Line 18
Debugging 19
Basic Programming Sample 20
Basic Kernel Code Sample 20
Basic Host Code Sample 21
Identifying Parallelizable Routines 29
Using Grand Central Dispatch With OpenCL 32
Discovering Available Compute Devices 32
Enqueueing A Kernel To A Dispatch Queue 33
Determining the Characteristics Of A Kernel On A Device 34
Obtaining the Kernel’s Workgroup Size 35
Sample Code: Creating a Dispatch Queue 37
Creating and Managing Memory Objects in OS X OpenCL 43
Overview 43
Workflow 43
Memory Visibility 44
Memory Consistency 46
Creating and Using Buffers in OpenCL 46
Representing Data With Buffer Objects 46
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
2Allocating Memory For Buffer Objects In OS X v10.7 47
Reading, Writing, and Copying Buffer Objects 48
Kernel Support For Data Processing In OpenCL-C 48
Releasing Buffer Objects 49
Setting the finalizer 49
Example: Allocating, Using, Releasing Buffer Objects 50
Creating and Using Images in OpenCL 54
Image Objects 54
Example 59
IOSurface and GL: What OpenCL Supports 64
How the Kernel Interacts With Data 64
Passing Data To a Kernel 64
Accessing Buffer Objects From a Kernel 64
Retrieving Results From a Kernel 65
OpenCL/ OpenGL Interoperation: Data Sharing 66
Sharegroups 67
Synchronizing Access To Shared OpenCL/OpenGL Objects 68
Example 68
Controlling OpenCL/OpenGL Interoperation With GCD 69
Using GCD To Synchronize A Host With OpenCL 69
Synchronizing A Host With OpenCL Using A Dispatch Semaphore 70
Synchronizing Multiple Queues 75
Using IOSurfaces With OpenCL 76
Creating Or Obtaining An IOSurface 76
Creating An Image Object from An IOSurface 76
Sharing the IOSurface With An OpenCL Device 77
Autovectorizer 79
Features 79
Without the Autovectorizer 79
Writing Optimal Code For the CPU: Let the autovectorizer do the work for you 81
Do 81
Don’t 81
What the autovectorizer does 81
Vectorization Example 81
Xcode 81
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
3
ContentsImproving Performance 82
Before Optimizing Code 82
Reducing Overhead 82
Measuring Performance 86
Measuring Performance On the Host 86
Measuring Performance On Devices 86
Estimating Optimal Performance 87
Tuning OpenCL Code For the CPU 89
In Practice 90
Tuning OpenCL Code For the GPU 99
In Practice 100
Document Revision History 108
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
4
ContentsFigures, Tables, and Listings
Developing OpenCL Programs Using Xcode 9
Figure 1-1 OpenCL Development Process 11
Hello World! 12
Figure 2-1 A simple OpenCL kernel in Xcode 12
Figure 2-2 Build settings for kernel files 13
Figure 2-3 OpenCL host code in Xcode 16
Figure 2-4 Adding the OpenCL framework 17
Figure 2-5 OpenCL framework was added 17
Figure 2-6 Results 18
Basic Programming Sample 20
Listing 3-1 Kernel code sample 20
Listing 3-2 Host code sample 21
Identifying Parallelizable Routines 29
Listing 4-1 Pseudocode that computes the final grade for each student 29
Listing 4-2 The isolated grade average task 30
Using Grand Central Dispatch With OpenCL 32
Listing 5-1 Creating a dispatch queue 37
Listing 5-2 Obtaining workgroup information 40
Creating and Managing Memory Objects in OS X OpenCL 43
Figure 6-1 Physical memory of an OpenCL system 45
Listing 6-1 Sample host function creates buffers then calls kernel function 50
Listing 6-2 Sample kernel squares an input array 54
Listing 6-3 Creating a 2D image object 55
Listing 6-4 An image-processing kernel function 58
Listing 6-5 Sample host function creates images then calls kernel function 59
Listing 6-6 Sample kernel swaps the red and green channels 63
OpenCL/ OpenGL Interoperation: Data Sharing 66
Figure 7-1 OpenGL and OpenCL share data using sharegroups 67
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
5Controlling OpenCL/OpenGL Interoperation With GCD 69
Figure 8-1 Rendering loop - each pass on the main thread creates a new frame for display 71
Listing 8-1 Synchronizing the host with OpenCL processing 70
Listing 8-2 Synchronizing a host with OpenCL using a dispatch semaphore 71
Listing 8-3 Synchronizing multiple queues 75
Using IOSurfaces With OpenCL 76
Listing 9-1 Creating an IOSurface-backed CL Image 77
Listing 9-2 Extracting an Image From an IOSurface 77
Autovectorizer 79
Figure 10-1 Before autovectorization: A simple float sent to the CPU and the GPU 80
Listing 10-1 Passing single floats into a kernel 80
Improving Performance 82
Figure 11-1 Memory copy speed in GB/s (read+write) vs buffer size 88
Table 11-1 Benchmarks of boxAvgH5 variants: 103
Table 11-2 Benchmarks of boxAvgH5 variants where each work item processes 4 columns: 106
Listing 11-1 Using the gettimeofday function 86
Listing 11-2 Sample benchmarking loop on the kernel 87
Listing 11-3 Kernel for estimating performance 87
Listing 11-4 The boxAvg kernel in two passes 91
Listing 11-5 Modify the horizontal pass to compute one row per work item instead of one pixel 92
Listing 11-6 Modify the algorithm to read fewer values per pixel and to incrementally update the sum 94
Listing 11-7 Modify the horizontal pass by moving division and conditionals out of the inner loop 95
Listing 11-8 Modify vertical pass to combine rows; each work item computes a block of rows 96
Listing 11-9 Ensure the image width is always a multiple of 4 97
Listing 11-10 A safer variant that will work for any image width 97
Listing 11-11 Fused kernel 98
Listing 11-12 Kernel before optimization 101
Listing 11-13 Move the data to local memory 102
Listing 11-14 Modify the kernel to compute several rows in each work item 103
Listing 11-15 Provide a dedicated kernel for each value of RANGE 104
Listing 11-16 Fastest variant: Unroll the inner loop and convert float data to float4 106
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
6
Figures, Tables, and ListingsOpenCL™ (Open Computing Language) is an open standard for cross-platform, parallel programming of modern
processors such as multicore CPUs and programmable GPUs. Introduced with OS X v10.6, OpenCL lets your
application tap into the parallel computing power of these processors to improve performance and deliver
features made possible by compute-intensive algorithms. OpenCL is comprised of thee parts: a C99-based
kernel programing language, a powerful scheduling API and a runtime that efficiently executes kernels on the
CPU or GPU.
Going beyond the standard, OS X v10.7 adds integration between OpenCL, Grand Central Dispatch and Xcode
making it even easer take advantage of the power of the OpenCL in your application.
At a Glance
Using OpenCL is easier than ever in OS X v10.7:
● OpenCL is fully supported by Xcode. The Xcode offline compiler removes a configuration step that used
to have to be performed before the kernel could be run and aids in debugging earlier in the development
process. See “Hello World!” (page 12).
● You can write OpenCL functions in separate files and include them in your Xcode project. These files can
be compiled as your application is built. This improves application performance because kernels need not
be compiled when the application is running
● OpenCL now integrates with Grand Central Dispatch, making it easier for you to focus on making your
OpenCL kernels more efficient. See “Using Grand Central Dispatch With OpenCL” (page 32).
● The autovectorizer is used for compiling kernels that will run on the CPU. It accelerates performance up
to four times without additional effort. The autovectorizer allows you to write one kernel that runs efficiently
on both a CPU and a GPU. It is invoked regardless of whether the openclc compiler is called from Xcode
or if the kernel is built at runtime. See “Autovectorizer” (page 79).
● You can, of course, continue to use code you’ve already written to the OpenCL 1.1 standard.
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
7
About Open CL for OS XPrerequisites
This guide assumes that you program in C and have access to The OpenCL Specification . Although this guide
discusses many key OpenCL API functions, it does not provide detailed information on the OpenCL API or the
OpenCL-C programming language.
See Also
The OpenCL Specification , available from the Khronos Group at http://www.khronos.org/registry/cl/ provides
information on the OpenCL standard.
The OpenCL Programming Guide by Aaftab Munshi, Benedict Gaster, Timothy G. Mattson, James Fung, and
Dan Ginsburg, available from Pearson Education, Inc.
For more information about Grand Central Dispatch queues, see Concurrency Programming Guide: Dispatch
Queues.
About Open CL for OS X
Prerequisites
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
8This chapter describes a streamlined process in which, using tools provided by OS X v10.7, you can include
OpenCL kernels as resources in Xcode projects, compile them along with the rest of your application, and use
Grand Central Dispatch as the queuing API for executing OpenCL commands and kernels on the CPU and GPU.
If you need to create OpenCL programs at run-time, with source loaded as a string or from a file, or if you want
API-level control over queueing, see The OpenCL Specification , available from the Khronos Group at
http://www.khronos.org/registry/cl/.
Concepts
In the OpenCL specification, computational processors are called devices. An OpenCL device has one or more
compute units. A workgroup executes on a single compute unit. A compute unit is composed of one or more
processing elements and local memory.
A Macintosh computer has a single CPU and GPUs. The CPU on a Macintosh has multiple compute units, which
is why it is called a multi-core CPU. The number of compute units in a CPU limits the number of workgroups
that can execute concurrently.
CPUs commonly contain two to eight compute units, with the maximum increasing year-to-year. A graphics
processing unit (GPU) typically contains many compute units—the GPUs in current Macintosh systems feature
tens of compute units, and future GPUs may contain hundreds. As used by OpenCL, a CPU with eight compute
units is considered a single device, as is a GPU with 100 compute units.
The OS X v10.7 implementation of the OpenCL API facilitates designing and coding data parallel programs to
run on both CPU and GPU devices. In a data parallel program, the same program (or kernel) runs concurrently
on different pieces of data and each invocation is called a work item and given a work item ID. The work item
IDs are organized in up to three dimensions (called an N-D range).
A kernel is essentially a function written in the OpenCL language that enables it to be compiled for execution
on any device thatsupports OpenCL. Although kernels are enqueued for execution by host applications written
in C, C++, or Objective C, a kernel must be compiled separately to be customized for the device on which it is
going to run. You can write your OpenCL kernel source code in a separate file or include it inline in your host
application source code.
OpenCL kernels can be:
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
9
Developing OpenCL Programs Using Xcode● Compiled at compile time, then run when queued by the host application
or
● Compiled and then run at runtime when queued by the host application
or
● Run from a previously-built binary
A work item is a parallel execution of a kernel on some data. It is analogousto a thread. Each kernel is executed
upon hundreds of thousands of work items
A workgroup is set of work items. Each workgroup is executed on a compute unit.
Workgroup dimensions determine how the input is operated upon in parallel. The application usually specifies
the dimensions based on the size of the input. There are constraints: for example, there may be a maximum
number of work items that can be launched for a certain kernel on a certain device.
The program that calls OpenCL functions to set up the context in which kernels run and enqueue the kernels
for execution is known asthe host application. The application isrun by OS X on the CPU. The device on which
the host application executes is known as the host device. Before kernels can be run, the host application
typically completes the following steps:
1. Determine what compute devices are available, if necessary.
2. Select compute devices appropriate for the application.
3. Create dispatch queues for selected compute devices.
4. Allocate the memory objects needed by the kernels for execution. (This step may occur earlier in the
process, as convenient.)
Note that the host device (the CPU) can itself be an OpenCL device and can be used to execute kernels.
The host application can enqueue commands to read from and write to memory objects. See “Creating and
Managing Memory Objects in OS X OpenCL” (page 43). Memory objects are used to manipulate device
memory. There are two types of memory objects used in OpenCL: buffer objects and image objects. Buffer
objects can contain any type of data; image objects contain data organized into pixels in a given format.
Developing OpenCL Programs Using Xcode
Concepts
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
10Essential Development Tasks
In OS X v10.7, the OpenCL development process includes these major steps:
Figure 1-1 OpenCL Development Process
Step 1:
Parallelize.
Step 2:
Code kernel.
Step 3:
Code host.
Step 4:
Compile.
Step 5:
Execute.
Step 6:
Debug.
Step 7:
Optimize.
1. Identify the tasks to be parallelized.
Determining how to parallelize your program effectively is often the hardest part of developing an OpenCL
program. See “Identifying Parallelizable Routines” (page 29).
2. In Xcode, write your kernel functions. See “Basic Kernel Code Sample” (page 20).
3. In Xcode, write the host code that will be calling the kernel(s). See “Basic Host Code Sample” (page 21).
4. Compile using Xcode. See “Creating An Application That Uses OpenCL In Xcode” (page 12).
5. Execute.
6. Debug (if necessary). See “Debugging” (page 19).
7. Improve performance (if necessary). See “Improving Performance” (page 82).
Developing OpenCL Programs Using Xcode
Essential Development Tasks
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
11Creating an OpenCL program in OS X v10.7 is easy with support built into Xcode. This chapter describes
step-by-step how to create an OpenCL project in Xcode. If you already have a working OpenCL project, you
need not regenerate it, but you can find information in this chapter about support for OpenCL now built into
Xcode.
Creating An Application That Uses OpenCL In Xcode
To create a project that uses OpenCL in OS X v10.7:
1. Create your OpenCL project in Xcode as a new OS X project (empty is fine).
2. Place your kernel code in one or more .cl files in your Xcode project. You can place all your kernels into a
single .cl file, or you can separate them as you choose. You can also include non-kernel code that will run
on the same OpenCL device as the kernel in each .cl file.
Each .cl file is compiled by default into three files containing bitcode for i386, x86_64, and gpu_32
architectures. You can change this using the OpenCL Architectures Build Setting.)
At runtime your host application discovers what kind(s) of devices are available, and determines which of
the compiled kernels to enqueue and execute.
Figure 2-1 A simple OpenCL kernel in Xcode
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
12
Hello World!3. You can set the following build settings for your kernel (.cl) files:
Figure 2-2 Build settings for kernel files
● OpenCL Compiler Version
● Compiler Version
The OpenCL C compiler version supported by the platform. The default is OpenCL C 1.1. To set
this parameter from the command line, use:
-cl-std=CL1.1
● OpenCL - Architectures
● Valid Architectures
A StringList specifying the list of the architectures for which the product will be built. This is
usually set to a predefined build setting provided by the platform. The default is that the product
is built for all three architectures. To set this parameter from the command line, use:
● -triple i386-applecl-darwin
● -triple x86_64-applecl-darwin
● -triple gpu_32-applecl-darwin
(So to compile for the first two, the command line would read:
-triple i386-applecl-darwin -triple x86_64-applecl-darwin
● OpenCL - Preprocessing
● Preprocessor Macros
Space-separated list of preprocessor macros of the form "foo" or "foo=bar". To set this
parameter from the command line, use:
-D
Hello World!
Creating An Application That Uses OpenCL In Xcode
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
13● OpenCL - Code Generation
● Use MAD
Boolean. If true, allow expressions of the type a * b + c to be replaced by a Multiply-Add (MAD)
instruction. If MAD is enabled, multistep instructions in the form a * b + c are performed in a
single step, but the accuracy of the results may be compromised. For example, to optimize
performance, some OpenCL devices implement MAD by truncating the result of the a * b
operation before adding it to c.
The default for this parameter is NO. To set this parameter from the command line, use:
-cl-mad-enable
● Relax IEEE Compliance
Boolean. If true, allows optimizations for floating-point arithmetic that may violate the IEEE 754
standard and the OpenCL numerical compliance requirements defined in in section 7.4 for
single-precision floating-point, section 9.3.9 for double-precision floating-point, and edge case
behavior in section 7.5 of the OpenCL 1.1 specification.
This is intended to be a performance optimization.
This option causes the preprocessor macro __FAST_RELAXED_MATH__ to be defined in the
OpenCL program. The default is NO. To set this parameter from the command line, use:
-cl-fast-relaxed-math
● Double as single
Boolean. If true, double precision floating-point expressions are treated as single precision
floating-point expressions. This option is available for GPUs only. The default is NO. To set this
parameter from the command line, use:
-cl-double-as-single
● Flush denorms to zero
Boolean that controls how single precision and double precision denormalized numbers are
handled. Ifspecified as a build option, the single precision denormalized numbers may be flushed
to zero; double precision denormalized numbers may also be flushed to zero if the optional
extension for double precision is supported. This is intended to be a performance hint and the
OpenCL compiler can choose not to flush denorms to zero if the device supports single precision
(or double precision) denormalized numbers.
This option isignored forsingle precision numbersif the device does notsupportsingle precision
denormalized numbers i.e. CL_FP_DENORM bit is not set in CL_DEVICE_SINGLE_FP_CONFIG.
This option isignored for double precision numbersif the device does notsupport double precision
or if it does support double precision but not double precision denormalized numbers i.e.
CL_FP_DENORM bit is not set in CL_DEVICE_DOUBLE_FP_CONFIG.
Hello World!
Creating An Application That Uses OpenCL In Xcode
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
14This flag only applies for scalar and vector single precision floating-point variables and
computations on these floating-point variables inside a program. It does not apply to reading
from or writing to image objects.
The default is NO. To set this parameter from the command line, use:
-cl-denorms-are-zero
● Auto-vectorizer
Auto-vectorizes the OpenCL kernels for the CPU. This setting takes effect only for the CPU. This
makes it possible to write a single kernel that is portable and performant across CPUs and GPUs.
The default is YES. To set this parameter from the command line, use:
-cl-auto-vectorize-enable
or
-cl-autovectorize-disable
● Optimization Level
You can choose whether to optimize for smallest code size or not.
The default is fast O1 optimization.
To set this parameter from the command line, use:
● -Os sets it to optimize for smallest code size
● -O, O1 sets it to fast
● -O2 sets it to faster
● -O3 sets it to fastest
● -O0 sets it to not optimize
Hello World!
Creating An Application That Uses OpenCL In Xcode
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
154. Place your host code in one or more .c files in your Xcode project.
Figure 2-3 OpenCL host code in Xcode
5. Link to the OpenCL framework.
a. Click on the target.
b. Click the Build Phases tab.
c. Open Link Binary With Libraries.
d. Click the + sign.
Hello World!
Creating An Application That Uses OpenCL In Xcode
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
16e. Select OpenCL.framework from the dropdown.
Figure 2-4 Adding the OpenCL framework
f. Press Add.
Figure 2-5 OpenCL framework was added
6. Build.
Hello World!
Creating An Application That Uses OpenCL In Xcode
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
177. Run.
Figure 2-6 Results
See “Basic Programming Sample” (page 20) for a line-by-line description of the host and kernel code in the
Hello World sample project.
Compiling From the Command Line
To compile from the command line, call openclc.
Hello World!
Compiling From the Command Line
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
18Debugging
Here are a few hints to help you debug your OpenCL application:
● Run your kernel on the CPU first. There is no memory protection on GPUs. If an index goes out of bounds
on the GPU, it is likely to take the whole system down. If an index goes out of bounds on the CPU, it may
crash the program that’s running, but it will not take the whole system down.
● You can use the printf function from within your kernel.
● You can use the gdb debugger to look at the assembly code once you’ve built your program. See GDB
website.
● On the GPU, use explicit addressrange checksto look for out-of-range address accesses. (Remember: there
is no memory protection on current GPUs.)
Hello World!
Debugging
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
19This chapter provides a tour through the code of a simple OpenCL application that performs calculations on
a test data set. The code in Listing 3-2 (page 21) calls the kernel defined in Listing 3-1 (page 20). The kernel
squares each value. Once the kernel completes its work, the host validates that every value was processed by
the kernel.
Basic Kernel Code Sample
Listing 3-1 (page 20) is example kernel code. See to download the project.
Listing 3-1 Kernel code sample
////////////////////////////////////////////////////////////////////////////////
// Simple OpenCL kernel that squares an input array.
// This code is stored in a file called mykernel.cl.
kernel void square(global float* input, global float* output) [1]
{
size_t i = get_global_id(0);
output[i] = input[i] * input[i];
}
Notes:
1. Wrap your kernel code into a kernel block:
kernel void kernelName (
global float* inputParameterName,
global float* [anotherInputParameter] ,
…,
global float* outputParameterName)
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
20
Basic Programming Sample{
...
}
Note: Kernels always return void.
Basic Host Code Sample
Listing 3-2 (page 21) is example code that would run on a host. It calls a kernel to square a set of values, then
tests to ensure that the kernel processed all the data. See to download the project.
Listing 3-2 Host code sample
////////////////////////////////////////////////////////////////////////////////
#include
#include
// This include pulls in everything you need to develop with OpenCL on OS X v10.7.
#include
// Include the header file generated by Xcode. This header file contains the
// kernel block declaration.
#include "mykernel.cl.h" [1]
// Hardcoded number of values to test, for convenience.
#define NUM_VALUES 1024
// A utility function that checks that our kernel execution performs the
// requested work over the entire range of data.
static int validate(cl_float* input, cl_float* output) {
int i;
for (i = 0; i < NUM_VALUES; i++) {
Basic Programming Sample
Basic Host Code Sample
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
21// The kernel was supposed to square each value.
if ( output[i] != (input[i] * input[i]) ) {
fprintf(stdout, "Error: Element %d did not match expected output.\n",
i);
fprintf(stdout, " Saw %1.4f, expected %1.4f\n", output[i],
input[i] * input[i]);
fflush(stdout);
return 0;
}
}
return 1;
}
int main (int argc, const char * argv[]) {
int i;
char name[128];
// First, try to obtain a dispatch queue that can send work to the
// GPU in our system. [2]
dispatch_queue_t queue = gcl_create_dispatch_queue(CL_DEVICE_TYPE_GPU,
NULL);
// In the event that our system does NOT have an OpenCL-compatible GPU,
// we can use the OpenCL CPU compute device instead.
if (queue == NULL) {
queue = gcl_create_dispatch_queue(CL_DEVICE_TYPE_CPU, NULL);
}
// This is not required, but let's print out the name of the device
// we are using to do work. We could use the same function,
// clGetDeviceInfo, to obtain all manner of information about the device.
cl_device_id gpu = gcl_get_device_id_with_dispatch_queue(queue);
clGetDeviceInfo(gpu, CL_DEVICE_NAME, 128, name, NULL);
fprintf(stdout, "Created a dispatch queue using the %s\n", name);
Basic Programming Sample
Basic Host Code Sample
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
22// Now we gin up some test data. This is typically the case: you have some
// data in your application that you want to process with OpenCL. This
// test_in buffer represents such data. Normally, this would come from
// some REAL source, like a camera, a sensor, or some compiled collection
// of statistics -- it just depends on the problem you want to solve.
float* test_in = (float*)malloc(sizeof(cl_float) * NUM_VALUES);
for (i = 0; i < NUM_VALUES; i++) {
test_in[i] = (cl_float)i;
}
// Once the computation using CL is done, we'll want to read the results
// back into our application's memory space. Allocate some space for that.
float* test_out = (float*)malloc(sizeof(cl_float) * NUM_VALUES);
// Our test kernel takes two parameters: an input float array and an
// output float array. We can't send the application's buffers above, since
// our CL device operates on its own memory space. Therefore, we allocate
// OpenCL memory for doing the work. Notice that for the input array,
// we specify CL_MEM_COPY_HOST_PTR and provide the fake input data we
// created above. This tells OpenCL to copy over our data into its memory
// space before it executes the kernel. [3]
void* mem_in = gcl_malloc(sizeof(cl_float) * NUM_VALUES, test_in,
CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR);
// The output array is not initalized; we're going to fill it up when
// we execute our kernel. [4]
void* mem_out = gcl_malloc(sizeof(cl_float) * NUM_VALUES, NULL,
CL_MEM_WRITE_ONLY);
// Dispatch your kernel block using one of the dispatch_ commands and the
// queue we created above. [5]
dispatch_sync(queue, ^{
// Though we COULD pass NULL as the workgroup size, which would tell
Basic Programming Sample
Basic Host Code Sample
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
23// OpenCL to pick the one it thinks is best, we can also ask
// OpenCL for the suggested size, and pass it ourselves. [6]
size_t wgs;
gcl_get_kernel_block_workgroup_info(square_kernel,
CL_KERNEL_WORK_GROUP_SIZE,
sizeof(wgs), &wgs, NULL);
// The N-Dimensional Range over which we'd like to execute our
// kernel. In our example case, we're operating on a 1D buffer, so
// it makes sense that our range is 1D.
cl_ndrange range = {
1, // The number of dimensions to use.
{0, 0, 0}, // The offset in each dimension. We want to
// process ALL of our data, so this is 0 for
// our test case. [7]
{NUM_VALUES, 0, 0}, // The global range -- this is how many items
// IN TOTAL in each dimension you want to
// process.
{workgroup_size, 0, 0} // The local size of each workgroup. This
// determines the number of workitems per
// workgroup. It indirectly affects the
// number of workgroups, since the global
// size / local size yields the number of
// workgroups. So in our test case, we will
// have NUM_VALUE / wgs workgroups.
};
// Calling the kernel is easy; you simply call it like a function,
// passing the ndrange as the first parameter, followed by the expected
// kernel parameters. Note that we case the 'void*' here to the
// expected OpenCL types. Remember -- if you use 'float' in your
// kernel, that's a 'cl_float' from the application's perspective. [8]
Basic Programming Sample
Basic Host Code Sample
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
24square_kernel(&range,(cl_float*)mem_in, (cl_float*)mem_out);
// Getting data out of the device's memory space is also easy; we
// use gcl_memcpy. In this case, we take the output computed by the
// kernel and copy it over to our application's memory space. [9]
gcl_memcpy(test_out, mem_out, sizeof(cl_float) * NUM_VALUES);
});
// Now we can check to make sure our kernel really did what we asked
// it to:
if ( validate(test_in, test_out)) {
fprintf(stdout, "All values were properly squared.\n");
}
// Don't forget to free up the CL device's memory when you're done. [10]
gcl_free(mem_in);
gcl_free(mem_out);
// And the same goes for system memory, as usual.
free(test_in);
free(test_out);
// Finally, release your queue just as you would any GCD queue. [11]
dispatch_release(queue);
}
Notes:
Basic Programming Sample
Basic Host Code Sample
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
251. Include the header file that contains the kernel block declaration. The name of the header file for a .cl file
will be the name of the .cl file with .h appended to it. For example, if the .cl file is named mykernel.cl,
the header file you must include will be mykernel.cl.h.
2. Call gcl_create_dispatch_queue to create the dispatch queue.
3. Create memory objects to hold input and output data and write input data to the input objects. Allocate
an array on the OpenCL device from which to read kernel results back into host memory. Use gcl_malloc
and make sure to use the OpenCL size of the datatype being returned (e.g.,
gcl_malloc(sizeof(cl_float) * NUM_VALUES). Because the CL device operates on its own memory
space, allocate OpenCL memory for the input data upon which the kernel will work. Specify
CL_MEM_COPY_HOST_PTR to tell OpenCL to copy over the input data from host memory into its memory
space before it executes the kernel.
4. Allocate OpenCL memory in which the kernel will store its results.
5. Dispatch your kernel block using one of the dispatch commands and the queue you created above. In
your dispatch call, you can specify workgroup parameters.
6. Describe the data parallel range over which to execute the kernel. You will describe the data-parallel range
for the OpenCL kernel in the host code. The cl_ndrange structure is used to specify the data parallel
range.
OpenCL always executes kernels in a data parallel fashion—that is, instances of the same kernel (work
items) execute on different portions of the total data set. See “Representing Data With Buffer Objects” (page
46). (If you want task-parallel execution you must enqueue multiple kernels on different devices.) Each
work item is responsible for executing the kernel once and operating on its assigned portion of the data
set. It is your responsibility to tell OpenCL the total number of work items that you need to process all of
your data. Because data sets are commonly organized in one, two, or three dimensions (representing such
things as audio data streams, two- or three-dimensional images, or three-dimensional objects), you also
need to indicate to OpenCL in how many dimensions your data extends (that is, how many coordinates
to use for each data point).
● Determining the Data Dimensions
The first step in preparing a kernel for execution is to identify the number of dimensions that you
want to use to represent your data. For example, if your data represents a flat image that is m pixels
wide by n pixels high, then you have a two-dimensional data set with each data pointed represented
by its coordinates on the m and n axes. On the other hand, if you’re dealing with spatial data that
involves the (x, y, z) position of nodes in three-dimensional space, you have a three-dimensional data
set. Another way to look at the dimensionality of your data is in terms of nested loops in a traditional,
non-parallel processing model. If you can loop through your entire data set with a single loop, then
your data is one-dimensional. If you would use one loop nested in another, your data is
Basic Programming Sample
Basic Host Code Sample
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
26two-dimensional, and if you would have loops nested three deep to cycle through all your data, your
data is three-dimensional. Whatever your data is, it’s up to you to determine how many dimensions
to use. As of OpenCL 1.0, dimensions greater than three are not supported.
● Determining the Number of Work Items
The next step in preparing your kernel for execution is determining how many work items you'll need
to process all of your data. This is known as the global work size, and it defines the total number of
work items needed for all dimensions combined. For one-dimensional data, the global work size
equals the number of data items. For two-dimensional data with m data items in one dimension and
n items in the second dimension, the global work size is n * m. Similarly, for three-dimensional data
with x , y , and z work itemsin the three dimensions, the global work size is x * y * z . There is practically
no limit on the number of work items, and this should be a large number (over 1000) for good
performance on GPUs.
● Choosing a Workgroup Size
When enqueuing a kernel to execute on a device, you can specify the size of the workgroup that you’d
like OpenCL to use during execution. A workgroup is a collection of work items that execute on the
same compute unit on the same OpenCL device. By providing OpenCL with a suggested workgroup
size, you are telling it how you would like it to delegate the work items to the various computational
units on the device. The work items executing in the same workgroup can share memory and execute
synchronously. In order to take advantage of these features, however, you have to know the maximum
workgroup size allowed by the OpenCL device on which your work items are executing. To get this
information, use the gcl_get_kernel_block_workgroup_info function and request the
CL_KERNEL_WORK_GROUP_SIZE property. This API isrequired to query the workgroup size of a kernel
block to use in cl_ndrange.local_work_size. Thisis needed for good performance across devices
asthe workgroup sizes vary across devices. This API must be called inside a block submitted to a Grand
Central Dispatch queue created using gcl_create_dispatch_queue. If you don’t need to share
data among work items, pass a NULL value to the local_work_size parameter when you enqueue
your kernel for execution to have OpenCL determine the workgroup size for you. Doing so will ensure
the most efficient use of the available devices.
Note that you also need to use clGetDeviceInfo with the selector
CL_DEVICE_MAX_WORK_ITEM_SIZES to get the maximum workgroup size in each dimension, and
call the gcl_get_kernel_block_workgroup_info function with the selector
CL_KERNEL_WORK_GROUP_SIZE to get the total workgroup size.
Three conditions must be met for the local dimensions to be valid:
a. The number of work items in each dimension (local_x, local_y, and local_z) in a single
workgroup must be less than the values returned for the device from
clGetDeviceInfo(CL_DEVICE_MAX_WORK_ITEM_SIZES).
Basic Programming Sample
Basic Host Code Sample
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
27b. The total number of work items in a workgroup (local_x times local_y times local_z
) must be less than or equal to the value returned by the
gcl_get_kernel_block_workgroup_info(CL_KERNEL_WORK_GROUP_SIZE) function.
c. The number of work items in each dimension in a single workgroup must divide evenly into the
total number of work items in that dimension ( global_n mod local_n = 0).
7. Always pass an offset for each of three dimensions even though the workgroup may have fewer than
three dimensions.
8. Call the kernel as you would call a function. Pass the ndrange as the first parameter, followed by the
expected kernel parameters. Case the void* types to the expected OpenCL types. Remember -- if you
use float in your kernel, that's a cl_float from the application's perspective. The call to the kernel will
look something like this:
kernelName(
&range,
(cl_datatype*)inputArray,
(cl_datatype *)outputArray );
9. Retrieve the data from the OpenCL device's memory space with gcl_memcpy. The output computed by
the kernel is copied over to the host application's memory space.
10. Free OpenCL memory objects.
11. Call dispatch_release(...) on the dispatch queue you created with gcl_create_dispatch_queue(...)
once you are done with it.
Basic Programming Sample
Basic Host Code Sample
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
28The first step in using OpenCL to improve your application’s performance is to identify what portions of your
application are appropriate for parallelization. Whereas in a general application you can spawn a separate
thread for a task as long as the functions in the thread are re-entrant and you’re careful about how you
synchronize data access, to achieve the level of parallelism for which OpenCL isideal, it is much more important
for the work items to be independent of each other. Although work items in the same workgroup can share
local data, they execute synchronously and so no work item’s calculations depend on the result from another
work item. Parallelization works only when the tasks that you run in parallel do not depend on each other.
For example, assume that you are writing a simple application that keeps track of the grades for students in a
class. The application consists of two main tasks:
1. Compute the final grade for each student, assuming the final grade is the average of all the students’
grades.
2. Obtain a class average by averaging the final grades of all students.
You cannot perform these two tasks in parallel because they are not independent of each other: to calculate
the class average, you must have already calculated the final grade for each student.
Despite the fact that you cannot perform task 1 and task 2 simultaneously, there is still an opportunity for
parallelization. To see how it can be broken down, it helpsto look at a basic pseudocode example for computing
the final grade for each student serially.
Listing 4-1 Pseudocode that computes the final grade for each student
// assume 'class' is a collection of 'student' objects
foreach(student in class)
{
// assume getGrades() returns a collection of integer grades
grades = student.getGrades();
sum = 0;
count = 0;
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
29
Identifying Parallelizable Routines// iterate through each grade, adding it to sum
foreach(grade in grades)
{
sum += grade;
count++;
}
// cache the average grade in the student object
student.averageGrade = sum / count;
}
The pseudocode in Listing 4-1 (page 29) proceeds through each student in the class, one by one, calculating
the average of each student’s grades and caching it in the student object. Although this example computes
each grade average one at a time, there’s no reason that the grade averages for all the students couldn’t be
calculated at the same time. Because the grades of one student do not affect the grades of another, you can
calculate the grade averages for all the students at the same time instead of looping through the same set of
instructions for each student, one at a time. This is the idea behind data parallelism.
Data parallelism consists of taking a single task (in this case, calculating a student’s average grade), and repeating
it over multiple sets of data. Students’ grades do not affect each other, therefore you can process them in
parallel. To express this programmatically, you must first separate your task (calculating the grade average of
a student) from your data (the students in the class). Listing 4-2 (page 30) shows how you can isolate the
grade-averaging task.
Listing 4-2 The isolated grade average task
task calculateAverageGradeForStudent( student )
{
// assume getGrades() returns a collection of integer grades
grades = student.getGrades();
sum = 0;
count = 0;
// iterate through each grade, adding it to sum
foreach(grade in grades)
Identifying Parallelizable Routines
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
30{
sum += grade;
count++;
}
// store the average grade in the student object
student.averageGrade = sum / count;
}
Now that you have the task isolated, you need to apply it to allstudentsin the classin parallel. Because OpenCL
has native support for parallel computing, you can rewrite the task shown in Listing 4-2 (page 30) as an OpenCL
kernel function. Using the OpenCL framework API, you can enqueue this kernel to run on a device where each
compute unit on the device can apply an instance of the kernel (that is, a work item) to a different set of data.
The challenge in parallelizing your application is identifying the tasks that you can distribute across multiple
compute units. Sometimes, asin this example, the identification isrelatively trivial and requiresfew algorithmic
changes. Other times, it might require designing a new algorithm from scratch that lends itself more readily
to parallelization. Although there is no universal rule for parallelizing your application, there are a few tips you
can keep in mind:
● Pay attention to loops.
Often the opportunities for parallelization lie within a subroutine that is repeated over a range of results.
● Nested loops might be restructured as multi-dimensional parallel tasks.
● Find as many tasks as possible that do not depend on each other.
Finding a group of routines that do not share memory or depend on each other’s results is usually a good
indicator that you can perform them in parallel. If you have enough such tasks, you can consider writing
a task-parallel OpenCL program.
● Due to the overhead of setting up a context and transferring data over a PCI bus, you must be processing
a fairly large data set before you see any benefits from using OpenCL. The exact point at which you start
to see benefits depends on the OpenCL implementation and the hardware being used, so you will have
to experiment to see how fast you can get your algorithm to execute. In general, a high ratio of computation
to data access and lots of mathematical computations are good for OpenCL programs.
Identifying Parallelizable Routines
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
31Developers already use Grand Central Dispatch (GCD) queues to implement concurrency in their applications.
OS X v10.7 adds the ability to enqueue work coded as OpenCL kernels to GCD queues backed by OpenCL
compute devices.
You can use GCD with OS X v10.7 OpenCL to:
●
Investigate the computational environment in which your OpenCL application is running.
You can use OS X v10.7 OpenCL to learn about the devicesin the system that would be best for performing
particular OpenCL computations and to enqueue kernels to devices:
● You can find out about the computational power and technical characteristics of each OpenCL-capable
device in the system. See “Discovering Available Compute Devices” (page 32).
● GCD can suggest which OpenCL device(s) would be best for running a particular kernel.
● You can obtain recommendations about how to configure the kernel - get the suggested optimal size
of the workgroup for each kernel on any particular device. See “Obtaining the Kernel’s Workgroup
Size” (page 35).
● Enqueue the kernel.
● Synchronize work between the host and OpenCL devices and synchronize work between devices.
Your host can wait on completion of work in all queues (See “Using GCD To Synchronize A Host With
OpenCL” (page 69)) or one queue can wait on completion of another queue (See “Synchronizing Multiple
Queues” (page 75)).
Discovering Available Compute Devices
OpenCL kernels assume a Single instruction, Multiple Data (SIMD) parallel model of computation. This means
(roughly) that you have a large amount of data divided into chunks, and you want the kernel to perform the
same computation on each chunk. Some SIMD algorithms will execute better on a CPU rather than on a GPU,
or on one GPU rather than another, depending on many factors. Tools in OS X version 7 and later facilitate
discovery of the types of devices that are available to process data.
A context is needed to share memory objects between devices. If you use The OS X v10.7 gcl_ APIs, you can
just retrieve and use the default global context; no context creation is needed.
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
32
Using Grand Central Dispatch With OpenCLNote: If you are using APIs defined in the OpenCL specification, you do need to create your own
contexts.
An OpenCL context is similar to an OpenGL sharegroup. A sharegroup is a set of tools that allow blocks of
memory to be accessed by both a GPU and a CPU. See “OpenCL/ OpenGL Interoperation: Data Sharing” (page
66).
When you retrieve the default global context in OS X v10.7 OpenCL, you can find out about the environment
in which OpenCL kernels execute. The context includes the set of devices, the memory accessible to those
devices, and one or more queues used to schedule execution of one or more kernels.
From the context, you can discover the types of devices in the system and can obtain recommendations as to
the optimal configuration for running a kernel. Your application can call on GCD to create a queue for a particular
type of device or to create a queue for a specific device.
1. Call the gcl_get_context function to get the "global" OpenCL context that OS X v10.7 creates for you.
Note: Since this context is created by the OpenCL, you should not retain / release it. (You should
retain/release any contexts that you explicitly create.)
2. Call the clGetDeviceIds( ... ) function (an API in the OpenCL standard API), specifying the context you
just obtained asthe context parameter. This call will return a list of the IDs of the OpenCL devices attached.
3. When you have the IDs of the devices in the context, you can call the clGetDeviceInfo() function for
each of the devices to obtain information about the device. The sample code in Listing 5-1 (page 37)
requeststhe vendor (the manufacturer) and the device name. You could also use the clGetDeviceInfo()
function to request more technical information like the number of compute cores, the cache line size and
so on. The types of information you can obtain are described in the OpenCL 1.1 specification. You can
choose to send different types of work to a device depending upon its characteristics and capabilities.
Enqueueing A Kernel To A Dispatch Queue
You must use an OpenCL-compatible dispatch queue for your OpenCL work. You can create a queue for a
particular device in the system or you can create a queue for a particular type of device. You can enqueue as
many kernels on each queue as you choose. You can create as many different queues as you would like:
● To create a dispatch queue to run on any device so long as it’s of a particular type, call the
gcl_create_dispatch_queue function passing CL_DEVICE_TYPE_CPU, CL_DEVICE_TYPE_GPU, or
CL_DEVICE_TYPE_ACCELERATOR as the first parameter.
Using Grand Central Dispatch With OpenCL
Enqueueing A Kernel To A Dispatch Queue
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
33Note: The dispatch queue you create must be attached to a particular device type. You cannot
create an OpenCL-compatible dispatch queue for the default device type
(CL_DEVICE_TYPE_DEFAULT).
OS X v10.7 OpenCL will create a dispatch queue that uses a GPU or CPU, depending upon the device type
you specified. If more than one GPU is available, OS X v10.7 OpenCL will give enqueue the kernel on the
device of the type you specify that has the largest number of compute cores.
Note: If you've created your dispatch queue specifying CL_DEVICE_TYPE_GPU, you won't
know whichGPUis being used. Callthe gcl_get_device_id_with_dispatch_queue function
to find out which device is actually attached to a given dispatch queue.
●
If you know exactly which OpenCL device id you want to use because you've obtained it with the
clGetDeviceIds function and found out about it using the clGetDeviceInfo function, call the
cl_create_dispatch_queue function with CL_DEVICE_TYPE_USE_ID and pass the id of the device
you want to use.
Both of these methods are illustrated in the sample code. See Listing 5-1 (page 37).
Note: Always call the dispatch_release(...) function on the dispatch queue you created with
the gcl_create_dispatch_queue(...) function once you are done with it. All of the example code
contains this call.
Once you have created a queue, you can enqueue as many kernels onto that queue as necessary. Or, you can
create additional queues with different characteristics.
For more information about Grand Central Dispatch queues, see Concurrency Programming Guide: Dispatch
Queues.
Determining the Characteristics Of A Kernel On A Device
To obtain information specific to a kernel/device pair, including how much private and local memory the kernel
will consume (on that device), as well as the workgroup size OpenCL thinks will be most optimal for execution,
call the gcl_get_kernel_block_workgroup_info function. Thisinformation is useful when you are tuning
performance for a particular device or debugging performance issues.
Using Grand Central Dispatch With OpenCL
Determining the Characteristics Of A Kernel On A Device
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
34Obtaining the Kernel’s Workgroup Size
To find out what OpenCL thinks is the best workgroup size for executing a kernel on a particular device, call
the gcl_get_kernel_block_workgroup_info function. You can use this value as the
cl_ndrange.local_work_size for a kernel on a particular device.
Note: You must call this API inside a block submitted to a GCD dispatch queue created using the
gcl_create_dispatch_queue function.
In Listing 5-1 (page 37), notice that we first execute this method in a block on a dispatch queue we've created
with OpenCL requesting the local memory size:
gcl_get_kernel_block_workgroup_info(
square_kernel,
CL_KERNEL_LOCAL_MEM_SIZE,
sizeof(local_memsize),
&local_memsize, NULL);
Then, in Listing 5-2 (page 40), we call the gcl_get_kernel_block_workgroup_info function to ask OpenCL
to return what it considers to be the optimal workgroup size for this kernel, on this device:
gcl_get_kernel_block_workgroup_info(
square_kernel, // this kernel
CL_KERNEL_WORK_GROUP_SIZE,
sizeof(workgroup_size), &workgroup_size, NULL);
fprintf(stdout, "Workgroup size: %ld\n",
workgroup_size);
Finally, we call the gcl_get_kernel_block_workgroup_info function to once more to ask OpenCL for a
workgroup size multiple. This is a performance hint based on the capabilities of the underlying device:
gcl_get_kernel_block_workgroup_info(
square_kernel, // this kernel
CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE,
sizeof(preferred_workgroup_size_multiple),
&preferred_workgroup_size_multiple, NULL);
Using Grand Central Dispatch With OpenCL
Obtaining the Kernel’s Workgroup Size
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
35You can now use these workgroup valuesto craft an appropriate cl_ndrange structure to use in launching
your kernel.
cl_ndrange range = {
1, // The number of dimensions to use.
{0, 0, 0}, // The offset in each dimension. We want to
// process ALL of our data, so this is 0 for
// our test case.
// Always pass an offset for each of the
// three dimensions even though the workgroup
// may have fewer than three dimensions.
{NUM_VALUES, 0, 0}, // The global range -- this is how many items
// IN TOTAL in each dimension you want to
// process.
// Always pass the global range for each of the
// three dimensions even though the workgroup
// may have fewer than three dimensions.
{workgroup_size, 0, 0 } // The local size of each workgroup. This
// determines the number of workitems per
// workgroup. It indirectly affects the
// number of workgroups, since the global
// size / local size yields the number of
// workgroups. So in our test case, we will
// have NUM_VALUE/workgroup_size workgroups.
// Always pass the workgroup size for each of the
// three dimensions even though the workgroup
// may have fewer than three dimensions.
};
Using Grand Central Dispatch With OpenCL
Obtaining the Kernel’s Workgroup Size
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
36Sample Code: Creating a Dispatch Queue
Listing 5-1 (page 37) demonstrates how to get the global OpenCL context, and how to ask that context about
the devices it contains. It also shows how to create a dispatch queue by asking for a device type (CPU or GPU),
and by specifying the queue's OpenCL device directly.
Listing 5-2 (page 40) shows how to obtain workgroup information -- useful for obtaining peak performance
-- from the kernel block.
Listing 5-1 Creating a dispatch queue
#include
// Include OpenCL/opencl.h to include everything you need for OpenCL
//development on OS X v10.7.
#include
// In this example, mykernel.cl.h is the header file that contains our kernel block
// declaration.
// This header file is generated by Xcode.
#include "mykernel.cl.h"
static void print_device_info(cl_device_id device) {
char name[128];
char vendor[128];
clGetDeviceInfo(device, CL_DEVICE_NAME, 128, name, NULL);
clGetDeviceInfo(device, CL_DEVICE_VENDOR, 128, vendor, NULL);
fprintf(stdout, "%s : %s\n", vendor, name);
}
#pragma mark -
#pragma mark Hello World - Sample 1
// Demonstrates how to get the global OpenCL context, and how to ask that
// context about the devices it contains. It also shows how
// to create a dispatch queue by asking for a device type (CPU or GPU) and
Using Grand Central Dispatch With OpenCL
Sample Code: Creating a Dispatch Queue
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
37// by specifying the queue's OpenCL device directly.
static void hello_world_sample1 ()
{
int i;
// Ask for the global OpenCL context:
// Note: If you will not be enqueing to a specific device, you do not need
// to retrieve the context.
cl_context context = gcl_get_context();
// Query this context to see what kinds of devices are available to us.
size_t length;
cl_device_id devices[8];
clGetContextInfo(
context, CL_CONTEXT_DEVICES, sizeof(devices), devices, &length);
// Walk over these devices, printing out some basic information. We could
// query any of the information available about the device here.
fprintf(stdout, "The following devices are available for use:\n");
int num_devices = (int)(length / sizeof(cl_device_id));
for (i = 0; i < num_devices; i++) {
print_device_info(devices[i]);
}
// To do any work, you need to create a dispatch queue associated
// with some OpenCL device. You can either let the system give you
// a GPU -- perhaps the only GPU -- or the CPU device. Or, you can
// create a dispatch queue with a cl_device_id you specify. This
// device id comes from the OpenCL context, as above. Below are three
// examples.
Using Grand Central Dispatch With OpenCL
Sample Code: Creating a Dispatch Queue
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
38// 1. Ask for a GPU-based dispatch queue; notice that we do not provide a
// device id - we let the system give us the most capable GPU.
dispatch_queue_t gpu_queue =
gcl_create_dispatch_queue(CL_DEVICE_TYPE_GPU, NULL);
// Get the device from the queue, so we can ask OpenCL questions about it.
// Note that we check to make sure there WAS an OpenCL-capable GPU in the
// system by checking against a NULL return value.
if (gpu_queue != NULL) {
cl_device_id gpu_device =
gcl_get_device_id_with_dispatch_queue(gpu_queue);
fprintf(stdout, "\nAsking for CL_DEVICE_TYPE_GPU gives us:\n");
print_device_info(gpu_device);
} else {
fprintf(stdout, "\nYour system does not contain an OpenCL-compatible "
"GPU\n.");
}
// 2. Let's try the same thing for CL_DEVICE_TYPE_CPU. All Macintosh
// systems will have a CPU OpenCL device, so we don't have to worry about
// checking for NULL, as we did in the case of a GPU.
dispatch_queue_t cpu_queue =
gcl_create_dispatch_queue(CL_DEVICE_TYPE_CPU, NULL);
cl_device_id cpu_device = gcl_get_device_id_with_dispatch_queue(cpu_queue);
fprintf(stdout, "\nAsking for CL_DEVICE_TYPE_CPU gives us:\n");
print_device_info(cpu_device);
// 3. Or perhaps you are in a situation where you want a specific device
// from the list of devices you found on the context.
Using Grand Central Dispatch With OpenCL
Sample Code: Creating a Dispatch Queue
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
39// Notice the difference here:
// We pass CL_DEVICE_TYPE_USE_ID and a device_id. We'll just use the
// first device on the context from above, whatever that might be.
dispatch_queue_t custom_queue =
gcl_create_dispatch_queue(CL_DEVICE_TYPE_USE_ID, devices[0]);
cl_device_id custom_device =
gcl_get_device_id_with_dispatch_queue(custom_queue);
fprintf(stdout,
"\nAsking for CL_DEVICE_TYPE_USE_ID and our own device gives us:\n");
print_device_info(custom_device);
// Now we could use any of these dispatch queues to run some kernels!
// Use the GCD API to free your queues.
dispatch_release(custom_queue);
dispatch_release(cpu_queue);
if (gpu_queue != NULL) dispatch_release(gpu_queue);
}
Listing 5-2 Obtaining workgroup information
#pragma mark -
#pragma mark Hello World - Sample 2
// This listing shows how to obtain workgroup info –
// useful for obtaining peak performance - from the kernel block.
static void hello_world_sample2() {
// Get a queue backed by a GPU for running our squaring kernel.
dispatch_queue_t queue =
Using Grand Central Dispatch With OpenCL
Sample Code: Creating a Dispatch Queue
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
40gcl_create_dispatch_queue(CL_DEVICE_TYPE_GPU, NULL);
// Did we get a GPU? If not, fall back to the CPU device.
if (queue == NULL) {
gcl_create_dispatch_queue(CL_DEVICE_TYPE_GPU, NULL);
}
// In any case, print out the device we're using:
fprintf(stdout, "\nExamining workgroup info for square_kernel on device ");
print_device_info(gcl_get_device_id_with_dispatch_queue(queue));
// Now find out what OpenCL thinks is the best workgroup size for
// executing this kernel on this particular device. Notice that we have
// to execute this method in a block, on a dispatch queue we've created
// with OpenCL.
dispatch_sync(queue,
^{
size_t wgs, preferred_wgs_multiple;
cl_ulong local_memsize, private_memsize;
// The next two calls give us information about how much
// memory, local and private, is used by the kernel on this
// particular device.
gcl_get_kernel_block_workgroup_info(square_kernel,
CL_KERNEL_LOCAL_MEM_SIZE,
sizeof(local_memsize),
&local_memsize, NULL);
fprintf(stdout, "Local memory size: %lld\n", local_memsize);
gcl_get_kernel_block_workgroup_info(square_kernel,
CL_KERNEL_PRIVATE_MEM_SIZE,
sizeof(private_memsize),
&private_memsize, NULL);
Using Grand Central Dispatch With OpenCL
Sample Code: Creating a Dispatch Queue
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
41fprintf(stdout, "Private memory size: %lld\n",
private_memsize);
// Here we ask OpenCL what it considers the optimal workgroup
// size for this kernel on this device.
gcl_get_kernel_block_workgroup_info(square_kernel,
CL_KERNEL_WORK_GROUP_SIZE,
sizeof(wgs), &wgs, NULL);
fprintf(stdout, "Workgroup size: %ld\n", wgs);
// Finally, we can ask OpenCL for a workgroup size multiple.
// This is a performance hint.
gcl_get_kernel_block_workgroup_info(square_kernel,
CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE,
sizeof(preferred_wgs_multiple),
&preferred_wgs_multiple, NULL);
fprintf(stdout, "Preferred workgroup size multiple: %ld\n",
preferred_wgs_multiple);
// You could now use these workgroup values to craft an
// appropriate cl_ndrange structure for use in launching
your kernel.
});
dispatch_release(queue);
}
int main(int argc, const char* argv[]) {
hello_world_sample1();
hello_world_sample2();
}
Using Grand Central Dispatch With OpenCL
Sample Code: Creating a Dispatch Queue
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
42This chapter provides an overview of how memory is used with OpenCL (since this differsfrom the way memory
is used in conventional programs), describes how buffers are created and used in OpenCL, describes how
images are created and used in OpenCL, and provides information about using memory in situations such as
with IOSurfaces and OpenGL textures.
Overview
Like all computational processes, processes that run on OpenCL devices consist of:
● Data
The data accessed by OpenCL instructions exists as memory buffers and cl_image memory objects. Use
image objects for representing 2D or 3D images (see “Creating and Using Images in OpenCL” (page 54));
use buffer objects for containing other types of generic data (see “Creating and Using Buffers in
OpenCL” (page 46)).
●
Instructions (in kernel functions) that manipulate the data.
Even if they are physically contiguous, host memory is distinct from OpenCL memory. Kernel instructions can
only access data in the memory of OpenCL devices. The host computer can read and write to device memory,
but only to set it up and retrieve results. During computation, a device looks only in device memory, and the
host stays out of its way.
In other words, in OpenCL, you launch a set of work items against a bolus of data. While this data might have
been passed to the OpenCL device by the host, the data resides on the OpenCL device at the time of execution.
A kernel cannot read or write to the host memory; it can only access data its own separate memory area. For
many devices(like GPUs), the OpenCL is actually a physically distinct piece ofsilicon. For other devices, although
the memory is physically on the same chip, it can only be read/written by the OpenCL kernel code.
Workflow
The basic workflow with OpenCL is:
1. Create memory objects for use by OpenCL
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
43
Creating and Managing Memory Objects in OS X
OpenCLThe host requests that memory be set aside for it on the device. The host can ask for as many memory
objects as it wants, up to the memory available.
2. Initialize the contents of the memory objects
a. The host can pass data to the device to be stored in its memory objects. Thisisthe data that the kernel
will process. The host can also instruct the device to leave some memory objects uninitialized so that
when the kernel runs on the device, it can fill up these memory objects as its output.
b. The host instructs the device to execute the kernel, passing it the memory objects it has created on
the OpenCL device as arguments. The host will wait until the kernel is done.
3. Execute the kernel
The kernel runs on the device, processing the data in the input memory, producing output to be stored
in the designated output memory.
4. Read results from the memory objects
When the host detects that the kernel has completed its tasks, it copies the results the kernel stored in
the designated output memory into memory the host can access."
5. Destroy the memory objects
Once the host has retrieved the output data, it instructs the device to free up the memory it had set aside
for the kernel to use.
Memory Visibility
In a typical multi-device environment, memory is distributed between devices. No device can access all memory.
For example, an OpenCL kernel resides in a separate memory space from the host that calls it. In order for the
kernel to accessthe data it isto process, the data must be moved into the device’s memory. However, transferring
data between memory areas to allow different devices to work can result in considerable overhead. Minimize
the amount of data being transferred to optimize performance.
The host specifies the memory space for a given buffer when it declares each kernel argument.
Creating and Managing Memory Objects in OS X OpenCL
Overview
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
44The memory spaces as an OpenCL device would see them are:
Figure 6-1 Physical memory of an OpenCL system
OpenCL Device
Host
Work Item
Local Memory
Workgroup
Global/Constant Memory
Host Memory
Private
Memory
Work Item
Private
Memory
Work Item
Local Memory
Workgroup
Private
Memory
Work Item
Private
Memory
● Private memory
Each work item has memory that only it can see. This is its private memory.
● Local memory
Local memory is memory that work items WITHIN a work group can share. Local memory is useful if more
than one work item in a group needs to use a particular chunk of global memory. You can write your
OpenCL program so that one work item loads from global memory to local memory, and then the rest of
the work items that need that piece of data can use the "local" copy. It takes GPU devices much less time
to access local memory than to access global memory.
● Global memory
This is the (relatively) massive chunk of device memory that ALL work items can "see". Any work item can
read / write to a buffer declared to be in global memory.
● Constant memory
Creating and Managing Memory Objects in OS X OpenCL
Overview
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
45Thisis a specialized section of global memory for data that you the programmer know will remain constant
throughout the execution of your kernel. It is more limited in size than global memory (typically), but is
faster to access on many devices than other global memory.
Memory Consistency
Changes a work item has made to global or local memory may not immediately become visible to other work
items within a workgroup. An system’s consistency has to do with when changes a work item has made to
global or local memory become visible to other work items within a workgroup.
OpenCL uses what is called a relaxed memory consistency model, which means that:
● Work items can access data within their own private memory, local memory, constant memory, and global
memory.
● Work items can share local memory during the execution of a workgroup. However, memory is only
guaranteed to be consistent after specific synchronization points.
If a work item needs to read something that another work item has written, then you will need to place
a barrier in your OpenCL code at the point where you want the memory to be consistent. The barrier will
stop any work item at the barrier until all other work items have "caught up". That's why it works -- every
work item in the workgroup has written its memory by that point, so it's safe to go on and read anything
any work item in the group has written. See “Using GCD To Synchronize A Host With OpenCL” (page 69).
Note: You can create a barrier that appliesto local memory or to global memory, but consistency
only appliesto work items within a work group. There is no such thing as a global memory barrier
that will make all threads in an execution wait. OpenCL only guarantees memory consistency
at a barrier within a work group.
Creating and Using Buffers in OpenCL
Representing Data With Buffer Objects
The OpenCL programming interface provides buffer objects for representing generic data in your OpenCL
programs. Instead of having to convert your data to the domain of a specific type of hardware, OpenCL enables
you to transfer your data as is to an OpenCL device via buffer objects and operate on the data using the same
language features that you are accustomed to in C.
Creating and Managing Memory Objects in OS X OpenCL
Creating and Using Buffers in OpenCL
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
46OpenCL is designed to share efficiently with OpenGL. Wherever possible, data is shared between OpenCL and
OpenGL programs; it is not copied.
Because transmitting data is costly, it is best to minimize reads and writes as much as possible. By packaging
all of your host data into a buffer object that can remain on the device, you reduce the amount of data traffic
necessary to process your data.
Allocating Memory For Buffer Objects In OS X v10.7
If you need a cl_mem object, (you will need one of these if you are going to be using a standard OpenCL API
call), call the gcl_malloc function to allocate the memory, then call the gcl_create_buffer_from_ptr
function to convert the handle gcl_malloc returns for use with the standard OpenCL API.
To create buffer objects:
● void * gcl_malloc(size_t bytes, void *host_ptr, cl_malloc_flags flags)
The gcl_malloc function returns a void * which is a memory object handle. The bytes parameter is
the number of bytes to be allocated. The host_ptr parameter . The flags parameter and can be 0 or
CL_MEM_USE_HOST_PTR.
Note: The void * value returned cannot be used to directly access the memory region on the
host CPU. To access this memory region for reading and writing on the host CPU, use APIs such
as cl_memcpy that can be passed in a block to GCD APIs that queue tasks for dispatch.
● cl_mem gcl_create_buffer_from_ptr(void *ptr)
The cl_mem gcl_create_buffer_from_ptr function creates a cl_mem buffer object from a ptr returned by
cl_malloc.
The cl_mem object returned can be used by CL API calls to enable sharing of objects between Grand CL
and the OpenCL API. The cl_mem object returned references the data store associated with the ptr
parameter.
Note: Be sure to release cl_mem objects created using gcl_create_buffer_from_ptr
before freeing this pointer using gcl_free.
Creating and Managing Memory Objects in OS X OpenCL
Creating and Using Buffers in OpenCL
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
47Reading, Writing, and Copying Buffer Objects
You can create memory objects outside a dispatch queue and the memory objects you create do not have to
be associated with any particular device. But before the memory object is accessed by OpenCL, it must be
associated with the device the data will be moving into and out of. You associate a memory object with its
device in the dispatch queue..
After you’ve created a buffer object, you can enqueue reads, writes, and copies. You can call the following
functions from your host application. These can be passed in a block(s) to the Grand Central Dispatch APIs that
queue tasks for dispatch such as dispatch_async. They enable you to move data to and from a host.
void gcl_memcpy(void *dst, const void *src, size_t size);
void gcl_memcpy_rect(void *dst, const void *src,
const size_t dst_origin[3],
const size_t src_origin[3],
const size_t region[3],
size_t dst_row_pitch,
size_t dst_slice_pitch,
size_t src_row_pitch,
size_t src_slice_pitch);
void *gcl_map_ptr(void *ptr, cl_map_flags map_flags, size_t cb);
void *gcl_map_image(cl_image image, cl_map_flags map_flags,
const size_t origin[3], const size_t region[3]);
void gcl_unmap(void *ptr);
Kernel Support For Data Processing In OpenCL-C
By associating your buffer object with specific kernel arguments, you make it possible to process your data
from the context of a kernel function. For example, in “Example: Allocating, Using, Releasing Buffer Objects” (page
50), notice how the code sample treats the input data pointer much as you would treat a pointer in C. In this
example the input data is an array of float values, and you can process each element of the float array by
indexing into the pointer. “Example: Allocating, Using, Releasing Buffer Objects” (page 50) does little more
than multiply a value by itself using the * operator, but OpenCL-C provides a wide array of data types and
operators that enable you to perform more complex arithmetic.
Creating and Managing Memory Objects in OS X OpenCL
Creating and Using Buffers in OpenCL
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
48Because OpenCL-C is based on C99, you are free to process your data in OpenCL-C functions as you would in
C with few limitations. Aside from support for recursion and function pointers, there are not many language
features that C has that OpenCL-C doesn’t have. In fact, OpenCL-C provides several beneficial features that the
C programming language does not offer natively, such as optimized image access functions.
OpenCL-C has built-in support for vector intrinsics and offers vector data types. The operators in OpenCL-C
are overloaded, and performing arithmetic between vector data typesissyntactically equivalent to performing
arithmetic between scalar values. Refer to the TheOpenCL Specification for more details on the built-in functions
and facilities of the OpenCL-C language.
Releasing Buffer Objects
To avoid memory leaks, free buffer objects when they are no longer needed. Call gcl_free to free buffer
objects created using gcl_malloc.
void gcl_free(void *ptr);
The ptr parameter is the handle of the buffer object to be released.
OpenCL uses a reference counting system to keep track of the memory objects currently being used. The
reference count represents how many other objects hold referencesto the particular memory object. Any time
you create a buffer object, it immediately receives a reference count of 1. Any time another object would also
like to maintain a reference to it, it should increment the buffer object’s reference count by calling the
clRetainMemObject function. When an object wishes to relinquish its reference to a buffer object, it should
call clReleaseMemObject. When the reference count for a buffer object reaches zero, OpenCL frees it,
returning the memory to the system and making any persisting references to the buffer object invalid.
Setting the finalizer
A finalizer is a function member of a reference class that is called automatically by the garbage collector when
destroying an object.
To specify which finalizer function the garbage collector calls for any objects created by gcl_malloc or
gcl_create_*** APIs (such as gcl_create_image), call:
void gcl_set_finalizer(void *object,
void (*gcl_pfn_finalizer)(void *object, void *user_data),
void *user_data);
Creating and Managing Memory Objects in OS X OpenCL
Creating and Using Buffers in OpenCL
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
49Example: Allocating, Using, Releasing Buffer Objects
In the following example, the host creates one input buffer and one output buffer, initializes the input buffer,
calls the kernel to square each value in the input buffer, then checks the results.
Listing 6-1 Sample host function creates buffers then calls kernel function
#include
#include
#include
// Include the automatically-generated header which provides the kernel block
// declaration.
#include "kernels.cl.h"
#define COUNT 2048
static void display_device(cl_device_id device)
{
char name_buf[128];
char vendor_buf[128];
clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(char)*128, name_buf, NULL);
clGetDeviceInfo(device, CL_DEVICE_VENDOR, sizeof(char)*128, vendor_buf, NULL);
fprintf(stdout, "Using OpenCL device: %s %s\n", vendor_buf, name_buf);
}
static void buffer_test(const dispatch_queue_t dq)
{
unsigned int i;
// We'll use a semaphore to synchronize the host and OpenCL device.
dispatch_semaphore_t dsema = dispatch_semaphore_create(0);
// Create some input data on the _host_ ...
Creating and Managing Memory Objects in OS X OpenCL
Creating and Using Buffers in OpenCL
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
50cl_float* host_input = (float*)malloc(sizeof(cl_float) * COUNT);
// ... and fill it with some initial data.
for (i=0; i
#include
#include
// Include the automatically-generated header which provides the kernel block
// declaration.
#include "kernels.cl.h"
#define COUNT 2048
static void display_device(cl_device_id device)
{
char name_buf[128];
Creating and Managing Memory Objects in OS X OpenCL
Creating and Using Images in OpenCL
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
59char vendor_buf[128];
clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(char)*128, name_buf, NULL);
clGetDeviceInfo(device, CL_DEVICE_VENDOR, sizeof(char)*128, vendor_buf, NULL);
fprintf(stdout, "Using OpenCL device: %s %s\n", vendor_buf, name_buf);
}
static void image_test(const dispatch_queue_t dq)
{
// As before, we use a dispatch semaphore to achieve synchronization between
// the host application and the work done for us by the OpenCL device.
dispatch_semaphore_t dsema = dispatch_semaphore_create(0);
// Let's create a "fake" RGBA, 8-bit-per channel image, solid red.
// In a real program, you would use some real raster data.
// Most OpenCL devices support a wide-variety of image formats.
unsigned int i;
size_t height = 2048, width = 2048;
unsigned int *pixels =
(unsigned int*)malloc( sizeof(unsigned int) * width * height );
for (i = 0; i < width*height; i++)
pixels[i] = 0xFF0000FF; // 0xAABBGGRR: 8bits per channel, all red.
// This image data is on the host side.
// We need to create two OpenCL images in order to perform some
// manipulations: one for the input and one for the ouput.
// This describes the format of the image data.
cl_image_format format;
format.image_channel_order = CL_RGBA;
Creating and Managing Memory Objects in OS X OpenCL
Creating and Using Images in OpenCL
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
60format.image_channel_data_type = CL_UNSIGNED_INT8;
cl_mem input_image = gcl_create_image(&format, width, height, 1, NULL);
cl_mem output_image = gcl_create_image(&format, width, height, 1, NULL);
dispatch_async(dq, ^{
// Our kernel is written such that each work item processes one pixel.
// Thus, we execute over a two-dimensional range,
// with the width and height of the image determining the dimensions
// of execution.
cl_ndrange range = {
2, // We're using a 2-dimensional execution.
{0}, // Start at the beginning of the range.
{width, height}, // Execute width * height work items.
{0} // And let OpenCL decide how to divide the work
items
// into work-groups.
};
// Copy the host-side, initial pixel data to the image memory object on
// the OpenCL device. We copy the whole image, but you could use the
// origin and region parameters to specify an offset and sub-region of
// the image, if you'd like.
const size_t origin[3] = { 0, 0, 0 };
const size_t region[3] = { width, height, 1 };
gcl_copy_ptr_to_image(input_image, pixels, origin, region);
// Do it!
red_to_green_kernel(&range, input_image, output_image);
// Read back the results; let's reuse the host-side buffer we started with.
gcl_copy_image_to_ptr(pixels, output_image, origin, region);
Creating and Managing Memory Objects in OS X OpenCL
Creating and Using Images in OpenCL
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
61// Let the host know we're done.
dispatch_semaphore_signal(dsema);
});
// Do other work, if you'd like...
// ... but eventually, you will want to wait for OpenCL to finish up.
dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER);
// Alright - we expect '0xFF00FF00' for each pixel. Solid green, all the way.
int results_ok = 1;
for (i = 0; i < width*height; i++) {
if (pixels[i] != 0xFF00FF00) {
fprintf(stdout,
"Oh no. Pixel %d was not correct. Expected 0xFF00FF00, saw %x\n",
i, pixels[i]);
results_ok = 0;
break;
}
}
if (results_ok)
fprintf(stdout, "Image results OK!\n");
// Clean up device-size allocations.
// Note that we use the "standard" OpenCL API here.
clReleaseMemObject(input_image);
clReleaseMemObject(output_image);
// Clean up host-side allocations.
free(pixels);
}
int main (int argc, const char * argv[])
Creating and Managing Memory Objects in OS X OpenCL
Creating and Using Images in OpenCL
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
62{
// Grab a CPU-based dispatch queue.
dispatch_queue_t dq = gcl_create_dispatch_queue(CL_DEVICE_TYPE_CPU, NULL);
if (!dq)
{
fprintf(stdout, "Unable to create a CPU-based dispatch queue.\n");
exit(1);
}
// Display the OpenCL device associated with this dispatch queue.
display_device(gcl_get_device_id_with_dispatch_queue(dq));
image_test(dq);
fprintf(stdout, "\nDone.\n\n");
dispatch_release(dq);
}
Listing 6-6 Sample kernel swaps the red and green channels
// A simple kernel that swaps the red and green channels.
const sampler_t sampler = CLK_NORMALIZED_COORDS_FALSE | CLK_FILTER_NEAREST;
kernel void red_to_green(read_only image2d_t input, write_only image2d_t output)
{
size_t x = get_global_id(0);
size_t y = get_global_id(1);
uint4 tap = read_imageui(input, sampler, (int2)(x,y));
write_imageui(output, (int2)(x,y), tap.yxzw);
}
Creating and Managing Memory Objects in OS X OpenCL
Creating and Using Images in OpenCL
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
63IOSurface and GL: What OpenCL Supports
How the Kernel Interacts With Data
Passing Data To a Kernel
Xcode uses your kernel code to automatically generate the kernel function prototype in the kernel header file.
To pass data to a kernel, when you call the kernel from your host code, passthe memory objects as parameters,
just as you would pass parameters to any other function. OpenCL kernel arguments can be scoped with a local
or global qualifier, designating the memory storage for these arguments. In OS X v10.7, for arguments to
OpenCL kernels that would have been declared with the local or __local address qualifier, the argument
type used in the block declaration of the kernel will be a size_t.
Consider the following kernel that has an argument declared with the local address qualifier:
kernel void foo(global float *a, local float *shared);
The extern declaration of this kernel block that will be generated for you in the host code will be:
extern void (^foo_kernel)(const cl_ndrange *ndrange,
float *a, size_t shared);
Accessing Buffer Objects From a Kernel
Once the data has been enqueued, in order for a device to actually process this data, you have to make this
data available to the work items that execute on the device. The following sections show you how to pass your
data to the compute kernels for further processing.
In your host application source code, it’s your responsibility to:
● Prepare the input data.
● Create a buffer object of the appropriate size.
● Move the input data from host memory. You can do this using the clCreateBuffer function by pointing
to the data on the host, or you can use the clEnqueueWriteBuffer function to enqueue a write from
host memory.
● Associate the input data with the kernel’s arguments. Use the clSetKernelArg function to do this.
Creating and Managing Memory Objects in OS X OpenCL
IOSurface and GL: What OpenCL Supports
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
64Kernels written in OpenCL-C need a data structure to describe the data parallel range over which to execute
the kernel. In OS X v10.7, you’ll use the cl_ndrange structure for this purpose:
typedef struct _cl_ndrange
{
size_t work_dim;
size_t global_work_offset[3];
size_t global_work_size[3];
size_t local_work_size[3];
} cl_ndrange;
Retrieving Results From a Kernel
To read the results back, call dispatch_sync. For example,
dispatch_sync(queue,
^{
gcl_memcpy(ptr_c, device_c,num_floats * sizeof(float));
});
Creating and Managing Memory Objects in OS X OpenCL
How the Kernel Interacts With Data
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
65OpenGL (Open Graphics Library) is an API for writing applications that produce 2D and 3D computer graphics.
See OpenGL.
OpenCL / OpenGL interoperability enables applicationsto share data between OpenCL and OpenGL efficiently.
You do not have to create multiple copies of the same content in OpenCL and OpenGL. An OpenCL memory
object created from an OpenGL object and the original OpenGL object both refer to the same memory and
both GLSL (OpenGL Shading Language) shaders and OpenCL kernels can access the shared data. Another
advantage of using OpenCL / OpenGL interoperability isthat the overhead of passing data for compute/display
purposes is greatly reduced. If the computation and rendering are performed on the GPU, the data need not
be moved between the host and the GPU.
This chapter describes the OpenCL APIs that can be used to create OpenCL memory objects from OpenGL
vertex buffer objects(VBOs), texture objects, and renderbuffer objects. An OpenCL buffer object may be created
from an OpenGL buffer object. An OpenCL image object may be created from an OpenGL texture or renderbuffer
object.
To create an OpenCL memory object from an OpenGL object, an OpenCL context has to be created from an
OpenGL share group (CGLShareGroup) object. An OpenGL share group object manages the OpenGL objects
on the devices in the rendering context. When an OpenCL context is connected to an OpenGL share group
object, both the OpenCL context and the OpenGL context can reference the same data objects.
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
66
OpenCL/ OpenGL Interoperation: Data SharingSharegroups
In the example illustrated in Figure 7-1 (page 67), OpenCL is used to generate geometry on the GPU and
OpenGL is used to render the shared geometry, also using the GPU. The OpenCL and OpenGL contextsreference
the same sharegroup (CGLShareGroupObj). Both OpenCL and OpenGL see the same devices and can access
the shared geometry. OpenGL sees the data as a VBO and OpenCL sees it as a buffer memory object.
Figure 7-1 OpenGL and OpenCL share data using sharegroups
CPU GPU
OpenCL CGLShareGroupObj
cl_context
cl_mem VBO
gl_context
To use OpenCL / OpenGL interoperability:
1. Set the sharegroup:
CGLContextObj cgl_context = CGLGetCurrentContext();
CGLShareGroupObj sharegroup = CGLGetShareGroup(cgl_context);
gcl_gl_set_sharegroup(sharegroup);
...
2. After the sharegroup has been set, you can create OpenCL memory objects from the existing OpenGL
objects:
● Use the following API to create an OpenCL buffer object from an OpenGL buffer object:
void * gcl_gl_create_ptr_from_buffer(GLuint bufobj);
● Use the following API to create an OpenCL image object from an OpenGL texture object:
OpenCL/ OpenGL Interoperation: Data Sharing
Sharegroups
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
67cl_image gcl_gl_create_image_from_texture(
GLenum texture_target,
GLint mip_level,
GLuint texture);
● Use the following API to create an OpenCL 2D image object from an OpenGL render buffer object:
cl_image gcl_gl_create_image_from_renderbuffer(GLuint render_buffer);
Synchronizing Access To Shared OpenCL/OpenGL Objects
To ensure data integrity, the application is responsible for synchronizing access to shared OpenCL/OpenGL
objects by their respective APIs. Failure to provide such synchronization may result in race conditions and
other undefined behavior including non-portability between implementations. For information about
synchronizing OpenCL and OpenGL events and fences, see “Controlling OpenCL/OpenGL Interoperation With
GCD” (page 69).
Example
OpenCL/ OpenGL Interoperation: Data Sharing
Synchronizing Access To Shared OpenCL/OpenGL Objects
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
68An application running on a host (a CPU) can route work or data (possibly in disparate chunks) to a device
using the standard OpenCL and OpenGL APIs and OS X v10.7 extensions. While the device does the work it
has been assigned, the host can continue working asynchronously. But at a certain point, the host will need
to wait for the results generated by the device performing the work it was assigned,so it will wait for the device
to notify it that the assigned work has been completed.
OpenCL and OpenGL can also share work and data. Typically, OpenCL will be used to generate or modify buffer
data which will then be rendered by OpenGL. Or, you might use OpenGL to create an image and then
post-process it using OpenCL. In either case, you have to make sure you synchronize correctly.
This chapter describes how to use GCD to synchronize:
● A host with OpenCL
See “Using GCD To Synchronize A Host With OpenCL” (page 69).
● A host with OpenCL using a dispatch semaphore
See “Synchronizing A Host With OpenCL Using A Dispatch Semaphore” (page 70).
● Multiple OpenCL Queues
See “Synchronizing Multiple Queues” (page 75).
You can still use the standard OpenCL and OpenGL APIs to obtain fine-grained synchronization when working
on shared data, where you either:
● Call OpenGL then OpenCL
● Call OpenCL then OpenGL
See the OpenGL and OpenGL specifications for more information.
Using GCD To Synchronize A Host With OpenCL
In Listing 8-1 (page 70), the host enqueues data in two queues to Grand Central Dispatch. The queued data
is processed while the host continues to do its own work. When the host needs the results, it waits for both
queues to complete their work.
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
69
Controlling OpenCL/OpenGL Interoperation With
GCDListing 8-1 Synchronizing the host with OpenCL processing
// Create a workgroup so host can wait for results from more than one kernel.
dispatch_group_t group = dispatch_group_create();
// Enqueue some of the data to the add_arrays_kernel on q0.
dispatch_group_async(group, q0,
^{ // Because the call is asynchronous,
// the host will not wait for the results
cl_ndrange ndrange = { 1, {0}, {N/2}, {0} };
add_arrays_kernel(&ndrange, a, b, c);
});
// Enqueue some of the data to the add_arrays_kernel on q1.
dispatch_group_async(group, q1,
^{ // Because the call is asynchronous,
// the host will not wait for the results
cl_ndrange ndrange = { 1, {N/2}, {N/2}, {0} };
add_arrays_kernel(&ndrange, a, b, c);
});
// Perform more work independent of the work being done by the kernels.
...
// At this point, the host needs the results before it can proceed.
// So it waits for the entire workgroup (on both queues) to complete its work.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
Synchronizing A Host With OpenCL Using A Dispatch Semaphore
The sample Listing 8-2 (page 71) illustrates how you can use OpenCL and OpenGL together in an application.
In this example, we create two vertex buffer objects (VBOs) using OpenGL (not shown). These VBOs represent
the positions of some objects in an N-body simulation. We then create OpenCL memory objects from these
VBOs (line [2]), which allows us to operate directly on the device memory containing this data in our OpenCL
kernel. We update these positions according to our desired algorithm, expressed as a per-object operation in
the included kernel, and then render the resulting VBO using OpenGL (commented, but not shown, at [4]).
Controlling OpenCL/OpenGL Interoperation With GCD
Synchronizing A Host With OpenCL Using A Dispatch Semaphore
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
70Since we are updating positions using OpenCL on a dispatch queue that runs asynchoronously with respect
to the thread that does the OpenGL rendering, we need to take action to ensure that we do not render before
the kernel has finished updating the positions. We utilize a mechanism that is common in applications that
require synchronization in GCD-compatible applications: a dispatch semaphore.
Before entering the main loop, we create a dispatch_semaphore_t (line [1]). In the block that we submit
to the dispatch queue created in OpenCL, just after our kernel call, we signal the semaphore. Meanwhile, the
"main" thread of execution has been rolling along -- perhaps doing more work -- eventually arriving at the call
to dispatch_semaphore_wait(...) (line [3]). The main thread stops at this point and waits until the
post-kernel signal "flips" the semaphore. Once that occurs, the code can continue to the OpenGL rendering
portion of the code, safe in the knowledge that the position update for this round is complete.
Figure 8-1 Rendering loop - each pass on the main thread creates a new frame for display
Synchronization point “I’m done”
OpenCL-created Dispatch Queue
integration_kernel(…)
Just might take
a bit of time
dispatch_semaphore_wait(…)
“Main” Thread of Execution
dispatch_async(…)
Note: that the main thread could do
other work here before stopping to wait.
But eventually, we call:
dispatch_semaphore_wait(…)
Here we sit and wait on CL to be done.
render_with_OpenGL(…)
glFlush(…)
Listing 8-2 Synchronizing a host with OpenCL using a dispatch semaphore
// In this case, the kernel code will update the position of the vertex.
...
// The host code is:
// Create the dispatch semaphone. [1]
Controlling OpenCL/OpenGL Interoperation With GCD
Synchronizing A Host With OpenCL Using A Dispatch Semaphore
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
71dispatch_queue_t queue;
dispatch_semaphore_t cl_gl_semaphore;
void *pos_gpu[2], *vel_gpu[2];
GLuint vbo[2];
float *host_pos_data, *host_vel_data;
int num_bodies;
int curr_read_index, curr_write_index;
// Extern OpenCL kernel declarations
extern void (^integrateNBodySystem_kernel)(const cl_ndrange *ndrange,
float4 *newPos, float4 *newVel,
float4 *oldPos, float4 *oldVel,
float deltaTime, float damping,
float softening, int numBodies,
size_t sharedPos);
void initialize_cl()
{
gcl_gl_set_sharegroup(CGLGetShareGroup(CGLGetCurrentContext());
// Create a CL dispatch queue.
queue = gcl_create_dispatch_queue(CL_DEVICE_TYPE_GPU, NULL);
// Create a dispatch semaphore used for CL / GL sharing.
cl_gl_semaphore = dispatch_semaphore_create(0);
// Create CL objects from GL VBOs that have already been created. [2]
pos_gpu[0] = gcl_gl_create_ptr_from_buffer(vbo[0]);
pos_gpu[1] = gcl_gl_create_ptr_from_buffer(vbo[1]);
vel_gpu[0] = gcl_malloc(sizeof(float4)*num_bodies, NULL, 0);
vel_gpu[1] = gcl_malloc(sizeof(float4)*num_bodies, NULL, 0);
Controlling OpenCL/OpenGL Interoperation With GCD
Synchronizing A Host With OpenCL Using A Dispatch Semaphore
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
72// Allocate and generate position and velocity data
// in host_pos_data and host_vel_data.
//
...
// Initialize CL buffers with host position and velocity data.
dispatch_async(queue,
^{gcl_memcpy(pos_gpu[curr_read_index], host_pos_data,
sizeof(float4)*num_bodies);
gcl_memcpy(vel_gpu[curr_read_index], host_vel_data,
sizeof(float4)*num_bodies);});
}
void execute_cl_gl_main_loop()
{
// Queue CL kernel to dispatch queue.
dispatch_async(queue,
^{
ndrange_t ndrange = { 1, {0}, {num_bodies} } ;
// Get local workgroup size that kernel can use for
// device associated with queue.
gcl_get_kernel_block_workgroup_info(
integrateNBodySystem_kernel,
CL_KERNEL_WORK_GROUP_SIZE,
sizeof(size_t), &nrange.local_work_size[0],
NULL);
// Queue CL kernel to dispatch queue.
integrateNBodySystem_kernel(&ndrange,
pos_gpu[curr_write_index],
vel_gpu[curr_write_index],
pos_gpu[curr_read_index],
vel_gpu[curr_read_index],
Controlling OpenCL/OpenGL Interoperation With GCD
Synchronizing A Host With OpenCL Using A Dispatch Semaphore
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
73damping, softening, num_bodies,
sizeof(float4)*ndrange.local_work_size[0]);
// Signal the dispatch semaphore to indicate that
// GL can now use resources.
dispatch_semaphore_signal(cl_gl_semaphore);});
// Do work not related to resources being used by CL in dispatch block.
// Need to use VBOs that are being used by CL so wait for the CL commands
// in dispatch queue to be issued to the GPU’s command-buffer. [3]
dispatch_semaphore_wait(cl_gl_semaphore, DISPATCH_TIME_FOREVER);
// Bind VBO that has been modified by CL kernel.
glBindBuffer(GL_ARRAY_BUFFER, pos_gpu[curr_write_index]);
// Now render with GL. [4]
// Flush GL commands.
glFlush();
}
void release_cl()
{
gcl_free(pos_gpu[0]);
gcl_free(pos_gpu[1]);
gcl_free(vel_gpu[0]);
gcl_free(vel_gpu[1]);
dispatch_release(cl_gl_semaphore);
dispatch_release(queue);
}
Controlling OpenCL/OpenGL Interoperation With GCD
Synchronizing A Host With OpenCL Using A Dispatch Semaphore
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
74Synchronizing Multiple Queues
In Listing 8-3 (page 75), the host enqueues data in two queues to Grand Central Dispatch. The second queue
waits for the first queue to complete its processing before doing its work. The host application does not wait
for completion of either queue.
Listing 8-3 Synchronizing multiple queues
// Create the workgroup which will consist of just the work items
// that must be completed first.
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
// Start work on the workgroup.
dispatch_async(q0,
^{
cl_ndrange ndrange = { 1, {0}, {N/2}, {0} };
add_arrays_kernel(&ndrange, a, b, c);
dispatch_group_leave(group);
});
// Simultaneously enqueue data on q1,
// but immediately wait until the workgroup on q0 completes.
dispatch_async(q1,
^{
// Wait for the work of the group to complete.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
cl_ndrange ndrange = { 1, {N/2}, {N/2}, {0} };
add_arrays_kernel(&ndrange, a, b, c);
});
// Host application does not wait.
Controlling OpenCL/OpenGL Interoperation With GCD
Synchronizing Multiple Queues
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
75An IOSurface is an abstraction for sharing image data. IOSurfaces are an efficient way to manage image
memory because when you use an IOSurface, if no copy is necessary, no time is wasted on making a copy.
An IOSurface transcends APIs, architectures, address spaces, and processes.
You can get an ID for an IOSurface that can be passed around from process to process, so that each of these
completely separate programs can use one single IOSurface. This makessharing an IOSurface between devices
very easy.
If you create an OpenCL image memory object from an existing IOSurface, you can modify the data contained
in the IOSurface either in your "main program" running on the CPU, or in an OpenCL kernel running on either
a GPU or a CPU.
Creating Or Obtaining An IOSurface
You can either create an IOSurface in code (see for an example) or you can request an IOSurface from another
running process such as Photo Booth. The underlying texture transfer mechanism for an IOSurface combines
GL_UNPACK_CLIENT_STORAGE_APPLE and GL_STORAGE_HINT_CACHED_APPLE together. The transfer is
done as a straight DMA to/from system memory and video memory with no format conversions of any kind
(other than some GPU-specific memory layout details). No matter how many different OpenGL contexts (in
the same process or not) bind a texture to an IOSurface, they all share the same system memory and GPU
memory copies of the data.
Creating An Image Object from An IOSurface
Once you’ve created or obtained an IOSurface, before you use it in OpenCL, you need to create an OpenCL
image memory object using the IOSurface. When you create the memory object, you are not making a copy;
the image memory object points at the same memory asthe original IOSurface. This makes using the IOSurface
very efficient.
If you are using GCD to interact with the IOSurface, create the IOSurface-backed CL image as shown in Listing
9-1 (page 77).
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
76
Using IOSurfaces With OpenCLListing 9-1 Creating an IOSurface-backed CL Image
cl_image gcl_create_image(
const cl_image_format *image_format,
size_t image_width,
size_t image_height,
size_t image_depth,
IOSurfaceRef io_surface);
// create a 2D image (depth = 0 or 1) or a 3D image (depth > 1).
// can also be used to create an image from an IOSurfaceRef.
If you are using the standard OpenCL API and not using GCD to create an IOSurface-backed CL object, use
clCreateImageFromIOSurface2D as shown in Listing 9-2 (page 77).
Listing 9-2 Extracting an Image From an IOSurface
cl_image_format image_format;
image_format.image_channel_order = CL_RGBA;
image_format.image_channel_data_type = CL_UNORM_INT8;
cl_mem image = clCreateImageFromIOSurface2D(
context, flags, image_format, width, height, surface, &err
);
Sharing the IOSurface With An OpenCL Device
Sharing an IOSurface in OpenCL is very simple. The key is to lock the IOSurface properly.
If your CPU (host) is going to modify the IOSurface and then share it with an OpenCL device, you should lock
the IOSurface before reading or writing to it, then unlock it before passing it to a kernel:
● The host creates or obtains the IOSurface and creates its CL image object .
●
If the host will be writing to the IOSurface, the host write-locks the IOSurface: IOSurfaceLock(...,
write type lock). If the host will only read from the IOSurface, the host read-locks it.
● The host writes to/reads from the IOSurface as necessary.
● The host unlocks the IOSurface: IOSurfaceUnlock(...). This tells the system that you changed the
data. You can then use the IOSurface-backed image in OpenCL -- the IOSurface object will handle any
necessary read locking internally for you.
Using IOSurfaces With OpenCL
Sharing the IOSurface With An OpenCL Device
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
77● The host enqueues the OpenCL kernel, passing it the IOSurface.
The locking and unlocking are simply the minimal calls needed to give OS X enough information to ensure
that each device always gets the latest, correct data.
If you will be using OpenCL to modify the IOSurface, you don't have to lock it. Just access the image memory
object directly.
Using IOSurfaces With OpenCL
Sharing the IOSurface With An OpenCL Device
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
78The autovectorizer detects operations in a scalar program that can be run in parallel and converts them into
sequential operations that can be handled efficiently by today's CPUs.
The autovectorizer frees you to write simple scalar code. It then vectorizes that code for you so that its
performance on the CPU is maximized while the same code runs on the GPU as well.
Note: Some GPUs also give higher performance when your code is vectorized. The autovectorizer
does not operate on GPU code, but you can vectorize your GPU code manually. If you do manually
vectorize your GPU code, test both vectorized and unvectorized versions to see which gives better
performance on specific hardware.
Features
● Runs by default when compiling to the CPU.
● Packs work items together.
● Generates a loop over the entire workgroup.
● Can provide performance improvement of up to the vector width of the CPU without additional effort.
● Allows you to write one scalar kernel that runs on CPU or GPU.
Without the Autovectorizer
The issue is that a GPU will process scalar data efficiently, but the CPU needs vectorized data to keep it fully
busy. Which means that, without the autovectorizer, you either have to write multiple device-specific kernels
that all perform the same function, or your performance will suffer.
OpenCL sees devices as having a number of compute cores and within them a number of processing elements.
When scalar code runs on the CPU, it will run on each core but will not take advantage of the vector unit.
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
79
AutovectorizerFor example, on a SSE4 machine, scalar code would run in one lane of the vector unit when it could be running
in four lanes. The monitor would report that the CPU is completely busy because all the cores are running, but
the CPU is actually only using a quarter of its vector width.
Figure 10-1 Before autovectorization: A simple float sent to the CPU and the GPU
CPU GPU
If you pass simple floats into a kernel:
Listing 10-1 Passing single floats into a kernel
kernel void add_arrays(global float* a, global float* b, global float* c)
{
size_t i = get_global_id(0);
c[i] = a[i] + b[i];
}
The kernel will be doing a scalar addition; operating on one data element at a time. If you send the scalar float
to the CPU and the GPU, the GPU will become fully engaged in processing the data. In the CPU, although all
the cores are busy, only one quarter of the vector width of the processing element in each core is used.
If you instead passin float4* parametersto the kernel, that makesthe addition a vector addition. The addition
is now CPU-only, specialized for that device. That would extract as much work as possible from the CPU but
leave the GPU idle.
In other words, without the autovectorizer, you would have to write multiple device-specific, non-scalar kernels,
one for each type of device.
Autovectorizer
Without the Autovectorizer
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
80Writing Optimal Code For the CPU: Let the autovectorizer do the
work for you
Do
● Write one simple (non-vectorized) kernel that can run on any device.
Don’t
● Write device-specific optimizations.
● Write work item ID-dependent control flow, if possible. (If this occurs in many places in the code, it would
likely prevent autovectorization from succeeding.)
What the autovectorizer does
● Runs by default whenever compiling kernels to a CPU.
● Packs work items together into vector instructions.
● Workgroup size can be increased if autovectorization is successful.
● Achieves performance improvements of up to the vector width of the CPU without additional effort on
your part.
Vectorization Example
Xcode
Setting Type Default Command Line Flag
-cl-auto-vectorize-enable
If this is set to NO, the command line flag should be
-cl-autovectorize- disable
Auto-vectorizer Boolean YES
Autovectorizer
Writing Optimal Code For the CPU: Let the autovectorizer do the work for you
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
81This chapter providessuggestions asto how to structure OpenCL code so that it runs most efficiently, describes
how to measure performance of OpenCL applications, and what to expect - how to set performance objectives.
It also provides an example of an iterative process of performance tuning of a simple image filter application.
Before Optimizing Code
Before you decide to optimize code, it is important to answer the following questions:
1. Does the code really need to be optimized? This is the most important question, and answering it is not
trivial when the OpenCL code is used inside a large application. Answering this question is out of the scope
of this document, but it should be considered seriously before starting any optimization effort.
2. How to measure the performance of the code?
3. What is the expected performance?
Reducing Overhead
Here are some general principles you can follow to improve the efficiency of your OpenCL code:
● Choose an efficient algorithm.
OpenCL can take advantage of all the devices in the system, but only if the algorithms in your program
are written to allow parallel processing.
Consider the following when choosing an algorithm:
● The algorithm should be massively parallel, so that the computation can be carried out by a large
number of independent work items:
For data parallel calculations on a GPU, OpenCL works best where many work items are submitted to
the device..
When sending work to a CPU, which typically has fewer cores than the GPU, it is important to match
the number of work items to the number of threads the CPU can effectively support.
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
82
Improving Performance● Most algorithms are memory-bound. Consequently, algorithms with the fewest memory accesses or
algorithms with a high compute:memory ratio are usually best for OpenCL applications. The
compute:memory ratio is the ratio between the number of floating-point operations and the number
of bytes transferred to and from memory.
● OpenCL is most efficient on large datasets. If possible, select an algorithm that works on large chunks
of data or merge several smaller tasks into one.
● For data parallel calculations on a GPU, OpenCL works best where there are a lot of work items
submitted to the device; however, some algorithms are much more efficient than others.
● Building an OpenCL program is computationally expensive and should ideally occur only once in a process.
Be sure to take advantage of tools in OS X v10.7 that allow you to compile once and then run many times.
If you do choose to compile a kernel during runtime, you will need to execute that kernel many times to
amortize the cost of compiling it. You can save the binary after the first time the program is run and reuse
the compiled code on subsequent invocations, but be prepared to recompile the kernel if the build fails
because of an OpenCL revision or a change in the hardware of the host machine.
You can also use bitcode generated by the OpenCL compiler instead of source code; compilation will be
much faster and you won’t have to ship source code with your application.
● Moving data to or from OpenCL devices is expensive.
OpenCL gives you complete control over allocation of memory and host-device memory transfers. Your
program will run much faster if you allocate memory on the OpenCL device, move your data to the device,
do as much computation as possible on the device, then move it off—rather than repeatedly going through
write-compute-read cycles.
● Allocating and freeing OpenCL resources (memory objects, kernels, etc.) takes time. Reuse these objects
whenever possible instead of releasing them and recreating them repeatedly. Note, however, that image
objects can be reused only if they are the same size and pixel format as needed by the new image.
● Local memory is faster than global memory and private memory is even faster.
When using memory on an OpenCL device, the local memory shared by all the work items in a single
workgroup is faster than the global memory shared by all the workgroups on the device. Private memory,
available only to a single work item, is even faster.
● Experiment with your code to find the kernel size that works best.
Using smaller kernels can be efficient because each tiny kernel uses minimal resources and breaking a job
down into many small kernels can allow for the creation of very large and efficient workgroups. On the
other hand, starting each kernel does take between 10-100 µs. When each kernel exits, the the results
must be stored in global memory. Because reading and writing to global memory is expensive,
concatenating many small kernels into one large kernel may save considerable overhead.
What kernel size is ideal for your application? To figure that out, you will have to experiment with your
code to find the kernel size that provides optimal performance.
Improving Performance
Reducing Overhead
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
83● OpenCL events on the GPU are expensive.
You can use eventsto coordinate execution between queues, but there is overhead to doing so. Use events
only where needed; otherwise take advantage of the in-order properties of queues.
● When tuning for performance, it's really easy to introduce subtle errors that make the code run faster but
produce bad output. After each iteration, always compare the output to a reference output computed on
the host. For the same reason, be sure to keep all revisions in case you realize that you need to revert your
code.
● Benchmark simple kernelsto estimate upper bounds and set optimization targets. See “Estimating Optimal
Performance” (page 87).
● Use OpenCL built-in functions whenever possible. Optimal code will be generated for these functions.
● Balance precision and speed. GPUs are designed for graphics, where the requirements for precision are
lower. The fastest variants are exposed in the OpenCL built-ins as fast_, half_, native_ functions. The
program build options allow control of some speed optimizations.
● Take advantage of the memory subsystem of the device:
● When writing for the CPU, take advantage of the memory subsystem: reuse data while it’s still in L1
or L2 cache. To achieve this, use loop blocking and access memory in a cache-friendly pattern.
● On the GPU, the memory access pattern is the most important factor. Use faster memory levels (local
memory, registers) to counter the effects of a sub-optimal pattern and to minimize accesses to the
slower global memory.
● Avoid divergent execution:
● The CPU predicts the result of conditional jump instructions (corresponding to if, for, while, etc.) and
starts processing the selected branch before knowing the effective result of the test. If the prediction
is wrong, the entire pipeline needs to be flushed, and we lose some cycles. If possible, use conditional
assignment instead.
● On the GPU, all threads scheduled together must execute the same code. As a consequence, when
executing a conditional, all threads execute both branches, with their output disabled when they are
in the wrong branch. It is best to avoid conditionals (replace them with a?x:y operators) or use
built-in functions.
● Know what kind of device your code is executing on.
OpenCL enables you to determine whether a device is a GPU or a CPU and how many devices are available.
You can optimize your code for the hardware on which it is running. The same OpenCL code may run
efficiently on both CPU and GPU, but optimal performance will usually require different code for each
device.
Improving Performance
Reducing Overhead
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
84GPUs and CPUs have fundamentally different architectures and so require different optimizations for
OpenCL. For example, whereas a CPU has a relatively small number of processing elements and a large
amount of memory (both a large cache and a much larger amount of RAM available on the circuit board),
a GPU has a relatively large number of processing elements and a comparatively small amount of memory.
● When writing for the CPU:
● Write simple scalar code first. The compiler and the autovectorizer work best on linear code and can
generate near-optimal code with no effort required from you. If the autovectorizer providessub-optimal
results, add vectors to the code by hand.
● Use the -cl-denorms-are-zero option in clBuildProgram, unless you need to use denormals
(denormals are very small numbers with a slightly different floating-point representation). Denormals
handling can be extremely slow (100x slower) and can lead to puzzling benchmark results.
● CPUs are not optimized for graphics processing. Avoid using images. CPUs provide no hardware
acceleration for images, and image access is slower than the equivalent buffer access.
See “Tuning OpenCL Code For the CPU” (page 89) for specific optimization strategies for CPUs.
● When writing for the GPU:
● Keep in mind that each family of GPUs has a unique architecture. To get the best possible performance
from a GPU, you need to understand that GPU’s architecture. For example, for a particular GPU it
might be more efficient to write to memory in blocks of a certain size, or it might be desirable to have
the number of work items in each workgroup a multiple of a particular number. Consult the literature
of the manufacturer of any GPU you wish to support to get details about that GPU’s architecture. This
document considers only general principles that should be true for any GPU.
● Avoid slow host-device transfers:
● Aggregate several transfers into a single, larger one.
● Design algorithms to keep the data on the device as long as possible.
● Try to maximize the compute/memory ratio and the number of independent dependency chains by
grouping the computation of several output elements into one single work item. The GPU has huge
computing power and kernels will usually be memory-bound.
● Try to use image objectsinstead of buffers. For certain memory access patterns, the different hardware
data path used when accessing images may be more efficient. This is especially the case when you
use 16-bit floating-point data (half).
See “Tuning OpenCL Code For the GPU” (page 99) for specific optimization strategies for GPUs.
Improving Performance
Reducing Overhead
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
85Measuring Performance
Execution time of OpenCL commands can be measured on the host or on the device.
It is best to measure performance on the host, because it is closer to the user-perceived execution time.
Measuring Performance On the Host
To measure OpenCL command execution time on the host:
1. Call the gettimeofday function to determine the start time. The gettimeofday function provides wall
clock time with microsecond granularity:
Listing 11-1 Using the gettimeofday function
#include
// Return wall clock time (s).
double getRealTime() {
struct timeval tv;
gettimeofday(&tv,0);
return (double)tv.tv_sec+1.0e-6*(double)tv.tv_usec;
}
2. Call clFinish(queue) to block the host thread until all the OpenCL commandsin the queue are executed.
3. When OpenCL processing completes, call the getTimeOfDay function to determine the elapsed time.
Measuring Performance On Devices
The following APIs allows you to measure time taken for various OpenCL commands and kernels queued in a
block to a dispatch queue.
● Start a timer
Call this function to start the timer:
cl_timer gcl_start_timer(void);
● Stop the timer
Call this function to stop the timer and return the elapsed time in seconds between when the call to
cl_start_timer associated with the timer parameter and when commands & kernelsin the block have
finished execution:
Improving Performance
Measuring Performance
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
86double gcl_stop_timer(cl_timer timer);
Measuring execution time of several consecutive calls to the same kernel(s) usually gives more reliable results.
“Warming-up” the device also improves consistency of benchmarking results. Listing 11-2 (page 87) shows
an example of benchmarking loop that can be included in kernel code:
Listing 11-2 Sample benchmarking loop on the kernel
const int iter = 10; // number of iterations to benchmark
cl_timer blockTimer;
for (int it=-2; it
}
clFinish(queue);
gcl_stop_timer(blockTimer);
// t = execution time for one iteration (s)
Estimating Optimal Performance
Before optimizing code, it is best to know what kind of performance is achievable.
The main factor determining the execution speed of an OpenCL kernel is memory usage. This is the case for
both CPU and GPU devices. Benchmarking the speed of the kernel function in Listing 11-3 (page 87) provides
a way to estimate the memory speed of an OpenCL device.
Listing 11-3 Kernel for estimating performance
kernel void copyBuffer(global const float * in,global float * out) {
int i = get_global_id(0);
out[i] = in[i]; // R+W one float
}
Improving Performance
Estimating Optimal Performance
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
87On our test machine we measured the following memory copy speeds for buffer sizes ranging from 1KiB to
16MiB:
Figure 11-1 Memory copy speed in GB/s (read+write) vs buffer size
100
1 KiB 4 KiB 16 KiB 64 KiB 256 KiB 1 MiB 4 MiB 16 MiB
75
50
25
0
Standard C library memcpy (running on one single thread)
The OpenCL code running on the CPU
The OpenCL code running on the GPU
Several interesting observations can be made from these curves:
● The measured cost of invoking the OpenCL kernels is in the 10-20 µs range, something like 50,000 CPU
clock cycles. For small tasks, it will be larger or comparable to the actual cost of the computation.
● The memcpy curve showsthe 4 different levels of the CPU memory hierarchy: L1 cache (90 GB/s), L2 cache
(50 GB/s), L3 cache (30 GB/s), and external memory (12 GB/s).
● The OpenCL GPU curve shows how GPU memory runs much faster than the CPU external memory. We
reach 36 GB/s for this mobile GPU, and some desktop GPUs can reach 160 GB/s.
Important: OpenCL is more efficient when data size increases. Try to process larger problems in fewer
kernel calls.
The asymptotic (maximum) value memory speed can be used to estimate the speed of a memory-bound
algorithm on large data.
Improving Performance
Estimating Optimal Performance
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
88Take, for example, the following boxAvg kernel. It takes a single channel floating point image as input, and
computes a single channel floating point image where each output pixel (x,y) is the average value of all pixels
in a square box centered at (x,y). A w by h image is stored in a buffer float * A, where pixel (x,y) is stored in
A[x+w*y].
Here is a first version of the code, before optimization:
constant int RANGE = 2;
kernel void boxAvg1(int w, int h, global const float * in, global float * out) {
int x = get_global_id(0); // pixel to process is (x,y)
int y = get_global_id(1);
float sumA = 0.0f; // sum of pixel values
float sum1 = 0.0f; // number of pixels
for (int dy=-RANGE;dy<=RANGE;dy++)
for (int dx=-RANGE;dx<=RANGE;dx++) {
int xx = x + dx;
int yy = y + dy;
// Accumulate if inside image
if (xx>=0 && xx=0 && yy=0 && xx=0 && yy=0 && xx= 0) { sumA -= inRow[x-RANGE-1]; sum1 -= 1.0f; }
if (x+RANGE < w) { sumA += inRow[x+RANGE]; sum1 += 1.0f; } // insert x+RANGE
// Store current value
out[x+w*y] = sumA/sum1;
Improving Performance
Tuning OpenCL Code For the CPU
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
94}
}
In this variant, we have reduced the memory accesses from 6 to 3 per iteration of the x loop. The execution
speed of this variant is now 1366 Mpix/s (and only 822 Mpix/s without the autovectorizer). This is 91% of our
upper bound.
We can move the conditionals and the division out of the x loop by splitting it into three parts:
Listing 11-7 Modify the horizontal pass by moving division and conditionals out of the inner loop
// Horizontal pass v4. Global work size: H
kernel void boxAvgH4(int w, int h, global const float * in, global float * out) {
int y = get_global_id(0); // row to process is y
global const float * inRow = in + y*w; // beginning of input row
global float * outRow = out + y*w; // beginning of output row
float sumA = 0.0f;
float sum1 = 0.0f;
// Left border
int x = -RANGE;
for (; x<=RANGE; x++) {
// Here, sumA corresponds to segment 0..x+RANGE-1, update to 0..x+RANGE.
sumA += inRow[x+RANGE]; sum1 += 1.0f;
if (x >= 0) outRow[x] = sumA/sum1;
}
// x is RANGE+1 here
// Internal pixels
float k = 1.0f/(float)(2*RANGE+1); // constant weight for internal pixels
for (; x+RANGE= number of CPU cores.
kernel void boxAvgV3(int w,int h,global const float * in,global float * out) {
// Numer of rows to process in each work item (rounded up)
int rowsPerItem = (h+get_global_size(0)-1)/get_global_size(0);
int y0 = rowsPerItem * get_global_id(0); // we update the range Y0..Y1-1
int y1 = min(h, y0 + rowsPerItem);
for (int y=y0; y= number of CPU cores.
// AUX[w*global_size(0)] is temporary storage, 1 row for each work item.
kernel void boxAvg2(int w, int h, global const float * in,
global float * out, global float * aux) {
// Number of rows to process in each work item (rounded up)
int rowsPerItem = (h+get_global_size(0)-1)/get_global_size(0);
int y0 = rowsPerItem * get_global_id(0); // we update the range Y0..Y1-1
int y1 = y0 + rowsPerItem;
aux += get_global_id(0) * w; // point to our work item’s row of temporary storage
float k = 1.0f/(float)(2*RANGE+1); // constant weight for internal pixels
// Process our rows. We need to process extra RANGE rows before and after.
for (int y=y0-RANGE; y=h) continue; // out of range
// Compute horizontal pass in AUX.
// The boxAvg4 code goes here on input row y
// The output is stored in AUX[W].
// Accumulate this row on output rows Y-RANGE..Y+RANGE
for (int dy=-RANGE; dy<=RANGE; dy++) {
int yy = y + dy;
if (yy < max(0, y0) || yy >= min(h, y1)) continue; // out of range
// Get number of rows accumulated in row YY, to get the weight
int nr = 1 + min(h-1, yy+RANGE)-max(0, yy-RANGE);
float u = 1.0f/(float)nr;
// Accumulate AUX in row YY
global float4 * outRow4 = (global float4 *)(out + w*yy);
global float4 * aux4 = (global float4 *)(aux);
for (int x=0; x<(w/4); x++) outRow4[x] += u * aux4[x];
}
}
}
Thisfused version runs at 1166 Mpix/s. Some rows will be processed twice,since we have to compute horizontal
filters on rows y0-RANGE to y1+RANGE-1 to update output rows y0 to y1-1. At any given time during the
execution, we will access one row in aux, one input row, and 2*RANGE+1 output rows. For a 4096x4096 image,
each row is 16 KiB, and all 7 rows fit in L2 cache.
Important: Merging two kernels called one after the other can reduce memory accesses, and works on
smaller data chunks fitting in faster cache levels, instead of of forcing the two kernels to resort to
communicate via via full round trips to external memory.
Tuning OpenCL Code For the GPU
The conditions to efficiently use a modern GPU are similar to the conditions we listed for the CPU, but with a
few notable differences. Efficient GPU optimization requires:
● Scheduling a large number of work items to use all resources and hide execution latency.
● Using the GPU memory hierarchy efficiently.
Improving Performance
Tuning OpenCL Code For the GPU
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
99The number of workgroups and work items required to efficiently utilize a GPU is much higher than for a CPU.
Inside the GPU we have a few cores (typically 2 to 16). Each core schedules work items in small groups (64
work items in the test machine). All these items are executed at the same time and can issue up to five
independent arithmetic ops. Instruction latency is much higher for the GPU. When all work items managed by
the core are waiting for the result of a previously issued instruction still in the pipeline, the GPU core stalls,
and we lose efficiency.
One way to avoid this is to increase the number of work items; another solution is to have more independent
dependency chains inside each work item.
The GPU memory hierarchy can be seen as four levels with the following orders of magnitude for access
bandwidth:
● Host memory accessed from the GPU through the PCI-Express bus, 10 GB/s
● OpenCL global memory, VRAM attached to the GPU, 100 GB/s
● OpenCL local memory, attached to each core, 1,000 GB/s
● OpenCL private memory, registers, 10,000 GB/s
Memory management is explicit: the host code manages host-device transfers and each variable belongs to
a unique address space (global, local, private, constant).
The most important factor in OpenCL efficiency is the memory access pattern: at a given time, we may have
hundreds of work items issuing a memory access instruction, each one with a different address. The hardware
is optimized to processsome of these patterns very quickly. Other access patterns can lead to hardware conflicts.
Hardware conflicts are resolved by serializing the accesses: they can’t occur in parallel,so the hardware schedules
them one after the other. A bad access pattern can make code run up to 30x slower.
A pattern where work item i accesses element x[i] of an array is fast. On the contrary, any pattern with a large
stride (especially a power of 2), x[s*i], will be extremely slow when s becomes large enough.
With such a large difference of bandwidth between the different layers, keeping reused data in the fastest
levels is another key to efficiency. In particular, it is best to avoid host-device transfers: keep data resident on
the GPU until it needs to be transferred to the host. If the output of OpenCL needs to be displayed, it is best
to use CL/GL interoperability and have the output image mapped to an OpenGL texture.
In Practice
We will tune the same boxAvg code we used in “Tuning OpenCL Code For the CPU” (page 89), but this time
for the GPU. We start from the same initial code for the horizontal pass as we did for the CPU:
Improving Performance
Tuning OpenCL Code For the GPU
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
100Listing
11-12
Kernel before optimization
// Horizontal pass v1. Global work size: w x h
kernel void boxAvgH1(int w,int h,global const float * in,global float * out) {
int x = get_global_id(0); // pixel to process is (x,y)
int y = get_global_id(1);
float sumA = 0.0f; // sum of pixel values
float sum1 = 0.0f; // number of pixels
for (int dx=-RANGE;dx<=RANGE;dx++) {
int xx = x + dx;
// Accumulate if inside image
if (xx>=0 && xx= 0 && xx < w)?in[xx+w*y]:0.0f;
}
// Block until all work items in the group finished updating AUX
barrier(CLK_LOCAL_MEM_FENCE);
// Compute our value
float sumA = 0.0f; // sum of pixel values
float sum1 = 0.0f; // number of pixels
for (int dx=-RANGE;dx<=RANGE;dx++) {
int xx = x + dx;
Improving Performance
Tuning OpenCL Code For the GPU
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
102sumA += aux[iid+dx+RANGE]; // will add 0 if out or range
sum1 += (xx >= 0 && xx < w)?1.0f:0.0f;
}
// Store output
out[x+w*y] = sumA/sum1;
}
In this example we are using all the work items in the workgroup to copy a segment of the input row to the
local memory buffer aux. Note that a barrier call is required to ensure all items in the group have actually
finished updating aux before we use the buffer.
This kernel runs slightly faster, at 1043 Mpix/s. It can be modified to process several consecutive rows inside
each work item, or several consecutive columns. The corresponding benchmarks are:
Table 11-1 Benchmarks of boxAvgH5 variants:
pix/item Mpix/s
1x1 1044
1x2 1630
1x4 1577
2x1 1300
4x1 1356
8x1 1123
Performance here is significantly improved, but is still far from the copy kernel reference speed of 4500 Mpix/s.
Let’s direct our attention to the vertical pass. If it proves to be much faster, we may be able to use it twice with
additional transpositions, assuming we can transpose an image efficiently.
The boxAvgV1 kernel presented in the CPU section runs at 884 Mpix/s. Let’s modify this kernel to compute
several rows in each work item:
Listing
11-14
Modify the kernel to compute several rows in each work item
// Vertical pass v4. Global work size: W x any
Improving Performance
Tuning OpenCL Code For the GPU
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
103kernel void boxAvgV4(int w, int h, global const float * in, global float * out) {
// Number of rows to compute
int rowsPerItem = (h+get_global_size(1)-1) / get_global_size(1);
int x = get_global_id(0); // column to process
int y0 = rowsPerItem * get_global_id(1); // rows to process are y0..y1-1
int y1 = min(h,y0+rowsPerItem);
float sumA = 0.0f; // sum of pixel values
float sum1 = 0.0f; // number of pixels
// Load values y0-RANGE-1..y0+RANGE-1
for (int y=max(0, y0-RANGE-1); y < min(h, y0+RANGE); y++) {
sumA += in[x+w*y]; sum1 += 1.0f;
}
// Process our rows
for (int y=y0; y= 0) { sumA -= in[x + w*yy]; sum1 -= 1.0f; }
yy = y+RANGE; if (yy < h) { sumA += in[x + w*yy]; sum1 += 1.0f; }
// Store value
out[x+w*y] = sumA/sum1;
}
}
This one runs at 2296 Mpix/s, and we read+write 2+1 float per pixel instead of 5+1. If we can provide a dedicated
kernel for each value of RANGE, we can reduce this to 1+1 float per pixel, by keeping a “ring” of previous
2*RANGE+1 values in registers. Doing so, we won’t need to reload the value for yy = y-RANGE-1 to remove it
from the sum. Here is the modified code:
Listing
11-15
Provide a dedicated kernel for each value of RANGE
// Register ring, RANGE=2
kernel void boxAvgV4_ring(int w,int h,global const float * in,global float * out)
Improving Performance
Tuning OpenCL Code For the GPU
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
104{
// Compute number of rows to compute
int rowsPerItem = (h+get_global_size(1)-1)/get_global_size(1);
int x = get_global_id(0); // column to process
int y0 = rowsPerItem * get_global_id(1); // rows to process are y0..y1-1
int y1 = min(h, y0+rowsPerItem);
float2 r0, r1, r2, r3, r4; // ring has 5 values
// Load values y0-RANGE-1..y0+RANGE-1 in the ring
int yy;
r0 = r1 = r2 = r3 = r4 = (float2)(0.0f);
yy = y0-2; if (yy>=0) r1 = (float2)(in[x + w*yy],1.0f);
yy = y0-1; if (yy>=0) r2 = (float2)(in[x + w*yy],1.0f);
yy = y0; r3 = (float2)(in[x + w*yy],1.0f);
yy = y0+1; if (yy=0) { r1 = in[x + w*yy]; s1 = 1.0f; }
yy = y0-1; if (yy>=0) { r2 = in[x + w*yy]; s2 = 1.0f; }
Improving Performance
Tuning OpenCL Code For the GPU
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
106yy = y0; { r3 = in[x + w*yy]; s3 = 1.0f; }
yy = y0+1; if (yy
This is a very short article.
The parser would report the following series of events to its delegate:
1. Started parsing document
2. Found start tag for element article
3. Found attribute author of element article, value “John Doe”
4. Found start tag for element para
5. Found characters This is a very short article.
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
6
Parser Capabilities and Architecture6. Found end tag for element para
7. Found end tag for element article
8. Ended parsing document
Both the tree-based and event-based parsing approaches have theirstrengths and disadvantages. It can require
considerable amounts of memory to construct an internal tree representing an XML document, especially if
that document is large. This problem is compounded if it becomes necessary to map the tree structure of the
parsed document to a more strongly typed, application-specific tree structure.
Event-driven parsing—because it deals with only one XML construct at a time and not all of them at
once—consumes much less memory than tree-based parsing. It is ideal for situations where performance is a
goal and modification of the parsed XML is not. One such application for event-driven parsing is searching a
repository of XML documents (or even one XML document with multiple “records”) for specific elements and
doing something with the element content. For example, you could use NSXMLParser to search the property-list
preferences files on all machines in a Bonjour network to gather network-configuration information.
Event-driven parsing is less suitable for tasks that require the XML to be subjected to extended user queries
or to be modified and written back to a file. Event-driven parsers such as NSXMLParser also do not offer any
help with validation (that is, it verifying whether XML conforms to the structuring rules as specified in a DTD
or other schema). For these kinds of tasks, you need a DOM-style tree. However, you can construct your own
internal tree structures using an event-driven parser such as NSXMLParser.
In addition to reporting parsing events, an NSXMLParser object verifies that the XML or DTD is well-formed.
For example, it checks whether a start tag for an element has a matching end tag or whether an attribute has
a value assigned. If it encounters any such syntactical error, it stops parsing and informs the delegate.
Although the parser “understands” only XML and DTD as markup languages, it can parse any XML-based
language schema such as RELAX NG and XML Schema.
Parser Capabilities and Architecture
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
7The essential steps for parsing an XML document using NSXMLParser are straightforward. It requires you
complete the following general steps:
1. Locate the XML.
Listing 1 shows code that lets the user select an XML file from a file-system browser (NSOpenPanel).
Listing 1 Opening an XML file
- (void)openXMLFile {
NSArray *fileTypes = [NSArray arrayWithObject:@"xml"];
NSOpenPanel *oPanel = [NSOpenPanel openPanel];
NSString *startingDir = [[NSUserDefaults standardUserDefaults]
objectForKey:@"StartingDirectory"];
if (!startingDir)
startingDir = NSHomeDirectory();
[oPanel setAllowsMultipleSelection:NO];
[oPanel beginSheetForDirectory:startingDir file:nil types:fileTypes
modalForWindow:[self window] modalDelegate:self
didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:)
contextInfo:nil];
}
- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode
contextInfo:(void *)contextInfo {
NSString *pathToFile = nil;
if (returnCode == NSOKButton) {
pathToFile = [[[sheet filenames] objectAtIndex:0] copy];
}
if (pathToFile) {
NSString *startingDir = [pathToFile
stringByDeletingLastPathComponent];
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
8
XML Parsing Basics[[NSUserDefaults standardUserDefaults] setObject:startingDir
forKey:@"StartingDirectory"];
[self parseXMLFile:pathToFile];
}
}
Although an XML file is the common case, the source of the XML might not be a file. You could receive
the XML from another object as a property-list object (such as an NSDictionary) or as a stream of bytes
over a network. In cases like these, you must convert the form of the XML to an NSData object before
initializing the NSXMLParser instance (see following step)
2. Create and initialize an instance of NSXMLParser., ensuring that you set a delegate.
Listing 2 illustrates how you might do this.
Listing 2 Creating and initializing a NSXMLParser instance
- (void)parseXMLFile:(NSString *)pathToFile {
BOOL success;
NSURL *xmlURL = [NSURL fileURLWithPath:pathToFile];
if (addressParser) // addressParser is an NSXMLParser instance variable
[addressParser release];
addressParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
[addressParser setDelegate:self];
[addressParser setShouldResolveExternalEntities:YES];
success = [addressParser parse]; // return value not used
// if not successful, delegate is informed of error
}
In this method, the client object converts the path to the XML file to an NSURL object and then uses that
object to initialize the NSXMLParser instance with initWithContentsOfURL:. It also sets the delegate
to be itself and letsthe parser know it wantsto resolve external entities(such as external DTD declarations).
Other NSXMLParser methodslet you set various namespace-related options. Finally, the clientsends parse
to the NSXMLParser instance to have it begin parsing the XML.
If the XML was in some form other than a file, you would convert it to an NSData object and then use the
initWithData: initializer:
addressParser = [[NSXMLParser alloc] initWithData:xmlData];
XML Parsing Basics
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
93. Implement the delegation methods that are of interest to you.
When the NSXMLParser object parses the XML, it sends a message to its delegate for each XML construct
it encounters (but only if the delegate implements the associated method). Implementations of these
methods vary by type of construct: DTD declarations, namespace prefixes, elements, and so on. Elements
are the most common type of XML construct processed;see “Handling XML Elements and Attributes” (page
11) for details.
All parsing operations begin with the delegate receiving parserDidStartDocument: and end with the
delegate receiving parserDidEndDocument: (assuming, of course,the delegate implementsthemethods).
The former method offers an opportunity for allocating and setting up resources needed for the parsing
operation; the latter method is a good place to release those resources and properly dispose of any result.
4. Handle any parsing errors.
If the parser encounters an error, it stops parsing and invokes the delegation method
parser:parseErrorOccurred:. Implement this method to interpret the error and inform the user. (All
parser errors are nonrecoverable.) See “Handling Parsing Errors” (page 17) for further information.
Memory management becomes a heightened concern when you are parsing XML. Processing the XML often
requires you to create many objects; you should not allow these objects to accumulate in memory past their
span of usefulness. One technique for dealing with these generated objects is for the delegate to create a local
autorelease pools at the beginning of each implemented delegation method and release the autorelease pool
just before returning. NSXMLParser managesthe memory for each object it creates and sendsto the delegate.
XML Parsing Basics
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
10Generally, when you parse an XML document most of the processing involves elements and things related to
elements, such as attributes and textual content. Elements hold most of the information in an XML document.
When the NSXMLParser object traverses an element in an XML document, it sends at least three separate
message to its delegate, in the following order:
parser:didStartElement:namespaceURI:qualifiedName:attributes:
parser:foundCharacters:
parser:didEndElement:namespaceURI:qualifiedName:
The parser might send the parser:foundCharacters: message multiple times for one element; however,
if the characters consist of nothing but white-space characters (space, new line, tab, and similar characters)
the parser sends parser:foundIgnorableWhitespace: instead.
When you are parsing XML elements, an advanced technique you can adopt is to switch processing
responsibilities among multiple delegates, each of which knows how to handle a certain type of element. For
more information see “Using Multiple Delegates” (page 19).
Design Considerations
In an object-oriented environmentsuch as Cocoa, a common strategy for handling elementsisto map them—at
the higher nesting levels, at least—to objects. Root elements and other top-level elements are frequently
equivalent to collections represented in Cocoa by NSDictionary and NSArray objects. Other elements might
readily map to one or more of an application’s custom model objects.
However, not all elements are best expressed as objects. Some lower level and particularly “leaf” elements are
more logically viewed as properties of their parent element (if that element maps to an object). And, of course,
you would probably make the actual attributes of any element a property (that is, an instance variable) of the
corresponding object.
Notwithstanding these suggestions, there is no ready-made mapping formula, and indeed your application
might not have to perform any element-to-object mapping to achieve its ends. These design decisions require
some thought as well as some familiarity with the structure of the XML.
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
11
Handling XML Elements and AttributesHandling an Element: An Example
The example code referred to in the following discussion processes an XML file containing personal-address
information and converts that information into Address Book objects (ABPerson and ABMultipleValue) that
can be added to a specified user’s address database. A portion of the XML looks like the following:
Listing 1 Some of the sample XML
Doe
John
(201) 345-6789
jdoe@foo.com
100 Main Street
Somewhere
New Jersey
07670
Let’s look at how the first three of these elements might be handled. When the parser first encounters these
elements, it invokes the delegate’s
parser:didStartElement:namespaceURI:qualifiedName:attributes: method. For the first two
elements, the delegate creates an equivalent object. For the third element (lastName), the delegate sets an
appropriate property of the second object. Listing 2 shows the delegate’s implementation for the start tags of
the first three elements.
Handling XML Elements and Attributes
Handling an Element: An Example
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
12Listing 2 Implementing parser:didStartElement:namespaceURI:qualifiedName:attribute:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict {
if ( [elementName isEqualToString:@"addresses"]) {
// addresses is an NSMutableArray instance variable
if (!addresses)
addresses = [[NSMutableArray alloc] init];
return;
}
if ( [elementName isEqualToString:@"person"] ) {
// currentPerson is an ABPerson instance variable
currentPerson = [[ABPerson alloc] init];
return;
}
if ( [elementName isEqualToString:@"lastName"] ) {
[self setCurrentProperty:kABLastNameProperty];
return;
}
// .... continued for remaining elements ....
}
The delegate identifies the element passed in (elementName), then processes it accordingly:
●
If it’s an addresses element (the root element) it creates a mutable array to hold the ABPerson objects.
This mutable array is held as an instance variable.
●
If it’s a person element, it creates an ABPerson object. This object is held as an instance variable named
currentPerson.
●
If it’s a lastName element, it sets an instance variable holding the current Address Book property; this
value is a enum constant declared in the Address Book framework.
The important action undertaken here is having a way (instance variables in this case) to track the current
element throughout the parser’s traversal of it. One reason for this importance is the semantics of
parser:foundCharacters:, most likely the next delegation method invoked. This method can be invoked
Handling XML Elements and Attributes
Handling an Element: An Example
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
13multiple times for the same element. In this method the delegate should append the characters passed in to
the characters accumulated so far for the element. The NSMutableString method appendString: is useful
for this purpose, as shown in Listing 3.
Listing 3 Implementing parser:foundCharacters:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (!currentStringValue) {
// currentStringValue is an NSMutableString instance variable
currentStringValue = [[NSMutableString alloc] initWithCapacity:50];
}
[currentStringValue appendString:string];
}
Again the code uses an instance variable (currentStringValue) as a way to track and gather the content
for the current element. If the parser encounters some white-space characters in the element content, it sends
the message parser:foundIgnorableWhitespace: to give the delegate the opportunity to retain any
white-space characters (such as tabs or new-lines).
Finally, when the parser encounters the end tag of an element, it invokes the delegation method
parser:didEndElement:namespaceURI:qualifiedName:. Listing 4 presents the approach taken by the
delegate in the example code.
Listing 4 Implementing parser:didEndElement:namespaceURI:qualifiedName:
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
// ignore root and empty elements
if (( [elementName isEqualToString:@"addresses"]) ||
( [elementName isEqualToString:@"address"] )) return;
if ( [elementName isEqualToString:@"person"] ) {
// addresses and currentPerson are instance variables
[addresses addObject:currentPerson];
[currentPerson release];
return;
}
NSString *prop = [self currentProperty];
Handling XML Elements and Attributes
Handling an Element: An Example
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
14// ... here ABMultiValue objects are dealt with ...
if (( [prop isEqualToString:kABLastNameProperty] ) ||
( [prop isEqualToString:kABFirstNameProperty] )) {
[currentPerson setValue:(id)currentStringValue forProperty:prop];
}
// currentStringValue is an instance variable
[currentStringValue release];
currentStringValue = nil;
}
If the delegate determines that the end tag is for the person element, it adds the ABPerson object to the
addresses array and releases the ABPerson object. If the end tag is for the lastName element (for example),
the delegate uses the ABRecord method setValue:forProperty: to set the appropriate property in the
ABPerson object (ABRecord isthe superclass of ABPerson). Finally, the instance variable holding the accumulated
content for the element (currentStringValue) is released.
Handling an Attribute
The addresses element shown in the example XML in Listing 1 (page 12) includes an attribute:
In this hypothetical case, the attribute allows the application parsing the XML to store the created Address
Book information in a specific user directory on a multi-user system.
The NSXMLParser object presents attributes of an element to the delegate in a dictionary in the final parameter
of parser:didStartElement:namespaceURI:qualifiedName:attributes:. Listing 5 shows how the
delegate in the example handles the owner attribute.
Listing 5 Handling an attribute of an element
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict {
Handling XML Elements and Attributes
Handling an Attribute
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
15if ( [elementName isEqualToString:@"addresses"]) {
// addresses is an NSMutableArray instance variable
if (!addresses)
addresses = [[NSMutableArray alloc] init];
NSString *thisOwner = [attributeDict objectForKey:@"owner"];
if (thisOwner)
[self setOwner:thisOwner forAddresses:addresses];
return;
// ... continued ...
}}
The delegate extracts the user name of the owner from the attributeDict dictionary using the attribute
name (owner) as a key. It then invokes a private method that associates the owner with the imported Address
Book data.
Handling XML Elements and Attributes
Handling an Attribute
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
16When the parser encounters a syntactical error or any other problem in an XML document that prevents it
from being well-formed, it stops parsing and sends a message to its delegate. The delegate, if it implements
the parser:parseErrorOccurred: method, receives this message. In its implementation it should display
a message informing users what the problem is. The parsing error is fatal (that is, unrecoverable) so informing
the user is all that you can realistically accomplish. With this information, the user might be able to fix the XML
so the document can be successfully parsed.
Listing 1 illustrates how you might implement parser:parseErrorOccurred:.
Listing 1 Handling parsing errors
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
NSWindow *modWin = [self windowForSheet];
if (!modWin) modWin = [NSApp mainWindow];
NSAlert *parserAlert = [[NSAlert alloc] init];
[parserAlert setMessageText:@"Parsing Error!"];
[parserAlert setInformativeText:[NSString stringWithFormat:@"Error %i,
Description: %@, Line: %i, Column: %i", [parseError code],
[[parser parserError] localizedDescription], [parser lineNumber],
[parser columnNumber]]];
[parserAlert addButtonWithTitle:@"OK"];
[parserAlert beginSheetModalForWindow:modWin modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
contextInfo:nil];
[parserAlert release];
}
- (void)alertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void
*)contextInfo { }
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
17
Handling Parsing ErrorsThe key line in this example is the one that constructs the NSAlert object’s informative text. This text includes
the error code (an NSXMLParserErrorenum constant), a localized description of the error, and a line number
and column (nesting level) number isolating the location of the error in the XML document. In the example,
the delegate obtains this information from two different sources: from the parser object itself (provided in the
first parameter of the method) or from the NSError object provided in the second parameter. From the parser
object it can also get an NSError object, and from that it can get a localized description.
However, the default localized description of NSError is rudimentary. You might want to provide your own
localized description instead of relying on the one obtained from the NSError object. Sometimes parsing errors
may require an application-specific interpretation. To implement a function or method for this purpose, you
can use the NSXMLParserError constant defining the error to determine which custom key to use in the
NSLocalizedString macro. (Of course, you must also create a strings file and do whatever else is necessary
to internationalize your application.)
Handling Parsing Errors
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
18For some XML documents, particularly large and complex documents, having a single delegate for the
NSXMLParser object might not be the best approach. The code for handling all of the different parsing events
can easily become overly intricate and hard to manage. One technique for making things more manageable
is to share the work of handling parsing events among multiple delegates.
Take as an example an application that constructs a DOM-style tree from elements as it encounters them.
Starting from the root element, one element creates a child element and passes off control to it by setting it
to be the delegate. That child element creates its children (and so on), each time resetting the delegate
appropriately. If an element has no children, or if it’s a mixed element, it accumulates the textual content for
itself. Finally, when the parser encounters an element’s end tag, the element sets the delegate to be its parent
element. Listing 1 shows the pertinent code that accomplishes this processing.
Listing 1 Resetting the delegate for the next element
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict {
// Element is a custom class for object representing element nodes
// Creation of element sets child as delegate (see below)
[self addChild:[Element elementWithName:elementName
attributes:attributeDict parent:self children:nil parser:parser]];
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
[self appendString:string];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
Element *parent = [self parent];
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
19
Using Multiple Delegates[parser setDelegate:parent]; // RESET DELEGATE TO PARENT
}
+ (id)elementWithName:(NSString *)elementName attributes:(NSDictionary *)attributes
parent:(Element *)parent children:(NSArray *)children parser:(NSXMLParser *)parser
{
return [[[[self class] alloc] initWithName:elementName
attributes:attributes parent:parent children:children
parser:parser] autorelease];
}
- (id)initWithName:(NSString *)elementName attributes:(NSDictionary *)attributes
parent:(id)parent children:(NSArray *)children parser:(NSXMLParser *)parser {
self = [super init];
if (self) {
[self setName:elementName];
if (attributes) {
[self addAttributes:attributes];
}
[self setParent:parent];
if (children) {
[self addChildren:children];
}
[parser setDelegate:self]; // CHILD SET AS DELEGATE
}
return self;
}
Another technique for managing multiple delegates is maintaining a number of delegate objects, each with
its specialized role, in a collection such as an NSDictionary object. These objects would know who their child
and parent elements are in any given context and so would be able to set the delegate for the next element
(using the appropriate dictionary key) after their work with the current element has finished.
Using Multiple Delegates
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
20Generally, if you wish to add or modify the content of an XML document, you must construct a static tree
structure that completely represents the elements and other constructs in the document. Tree representations
are also essential if you intend to validate an XML document against the DTD (or other language schema) that
prescribes the logical structure of the document.
When most developers want to construct DOM-style tree representations of XML documents, they use a
tree-based parser, not a streaming parser such as NSXMLParser. (Tree-based parsing engines, however, are
typically built on top of streaming parsers.) Nonetheless, that does not mean that you cannot create tree
structures using an NSXMLParser instance. Although this article does not go into great detail about techniques
for constructing XML tree structures using NSXMLParser, it outlines a general approach that you could take.
Note: DOM (for Document Object Model) is a model proposed by the World Wide Web Consortium
for describing XML and HTML documents using a standard set of objects. It also defines an interface
for accessing and manipulating those objects, which represent (among other things) the elements
of a document and the attributes associated with each element. The procedure discussed below
does not make specific use of DOM, although there are similarities.
You can represent any XML document as a hierarchical tree whose “nodes” are elements exhibiting relationships
of parent and child with other elements. Each element can have one or more children and, with the exception
of the root element, has exactly one parent element. The tree is anchored by a root element, which is the only
element in the tree without a parent. The “leaf” nodes of the tree are typically those elements containing
nothing but text, although they can also be mixed elements or empty elements.
For example, consider the following short XML document:
Doe
John
(201) 345-6789
jdoe@foo.com
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
21
Constructing XML Tree Structures
100 Main Street
Somewhere
New Jersey
07670
The following tree of element nodes represents this document:
Figure 1 Tree representation of simple XML document
addresses
person
lastName firstName phone email address
street city state zip
There are several possible ways to construct a tree representation of an XML document using NSXMLParser.
This article, however, looks at a recursive, object-oriented approach that dynamically transfers delegation
responsibilities among the objects representing the elements of a document. (This strategic shifting of the
NSXMLParser delegate is discussed further in “Using Multiple Delegates” (page 19).) The programmatic result
is doubly-linked lists of objects and arrays of objects; the abstract result is a tree representation of the document.
The procedure for constructing a tree using this approach entails the following steps:
1. Create a class whose instances represent the elements of an XML document. The class should define the
name of the element and its parent (one-to-one) and children (one-to-many) relationships; it should also
encapsulate the attributes associated with the element. As a shorthand notation for this procedure, we’ll
call this class MyElement.
Constructing XML Tree Structures
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
222. From a top-level object in the application, load an XML document, create an NSXMLParser instance for it,
assign the top-level object as delegate, and begin parsing the document (see “XML Parsing Basics” (page
8)).
3. The parser encounters the document’s root element first and sends
parser:didStartElement:namespaceURI:qualifiedName:attributes: to its delegate. The
delegate creates a MyElement object to represent thisroot element and setsits parent to nil. The method
that creates and initializes the object also sets it to be the new delegate of the NSXMLParser instance.
4. The parser encounters the next element of the document—the first child of the root element—and again
sendsthe delegate parser:didStartElement:namespaceURI:qualifiedName:attributes:. The
delegate is now the MyElement object recently created to represent the root element. It creates another
MyElement object to represent the new element (in the process setting the new object to be the delegate
and setting itself to be the parent) and adds the new object to its list of children.
5. The new delegate receives the next
parser:didStartElement:namespaceURI:qualifiedName:attributes: message, identifying its
first child element, and it creates it and adds it to its list of children.
6. Thisrecursive descent through the first branch of the tree ends when the parser encounters“leaf” elements
containing text, mixed content, or empty elements. If there is mixed content the descent is not truly over
since parser:didStartElement:namespaceURI:qualifiedName:attributes: is sent to the
delegate even after it receives parser:foundCharacters: for the current element. Processing depends
on the kind of element:
●
If it’s an empty element, processing skips ahead to the next step (end-element tag)
●
If there is only text associated with the current element node, the delegate responds to the
parser:foundCharacters: message by accumulating text (in sequential
parser:foundCharacters: invocations).
●
If there is mixed content, the delegate will process the text even after it receives messages notifying
it of the start-element and end-element tags for the embedded elements. One way to handle this is
to wrap the text in special text-element objects and insert these (in the proper order) in the element’s
child list.
7. Finally,the parsersendsthe parser:didEndElement:namespaceURI:qualifiedName: to the delegate,
notifying it that the element is now complete. The delegate sets the new delegate to be its parent and
returns.
8. If the parent has more children elements, the parser sends it the next
parser:didStartElement:namespaceURI:qualifiedName:attributes: message; the parent
MyElement object creates a MyElement instance to represent its next child (in the process setting it to be
the new delegate and setting itself to be the parent of the new MyElement) and adds the newly created
Constructing XML Tree Structures
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
23object to its list of children. However, if the parent has no more children to add to its list (that is, it receives
the parser:didEndElement:namespaceURI:qualifiedName: message instead) it sets the new
delegate to be its parent and returns.
9. The procedure continues in this fashion until the entire XML document is processed and all branches of
the tree are constructed.
The objects that are the nodes of the tree (representing mostly elements) should be able to print themselves
out as XML code. Your application should also implement an algorithm that asksthe objectsto print themselves
in the proper document sequence.
Constructing XML Tree Structures
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
24Validation is a procedure that ensures an XML document conforms to the rules governing its logical structure
as specified in a language schema such as DTD (Document Type Definition). An XML document might be
well-formed—that is, it obeys the syntactical rules of XML—and at the same time be invalid. For example, an
element might include a child element when it issupposed to have only textual content, or a required attribute
of an element might be missing.
To perform validation it helpsto construct a tree of an XML document’sschema that is parallel to a tree structure
representing the document’s actual content (see “Constructing XML Tree Structures” (page 21)). The schema
tree presents a simple abstract view of how the document should be structured. Instead of nodes of objects
representing the actual elements and text of the document, the schema tree contains nodes that express the
rules by which the parts of the document can be combined. Validation tests the actual elements, attributes,
and other parts of the document against the rules of the schema to see if the document conforms. If your
application finds any violation of conformance, it can notify the user and perhaps require the user to fix the
error. You can validate an XML document when it is first read and processed and later when users attempt to
make any changes to it.
Because the programmatic interface of NSXMLParser is designed to report only XML constructs and DTD
declarations, this article focuses on that language schema. However, if you use an XML-based language schema,
such as RELAX NG, then NSXMLParser can process the schema just it would as any XML file, reporting what it
finds to its delegate. You can use the data you thereby acquire for validation.
The sections on constructing rules focus primarily on element and attribute declarations because these are by
far the most common and most important type of declaration. “Handling Other Declarations” (page 29) briefly
discusses what to do with other kinds of declarations, such as those for entities and notations.
Using NSXMLParser to Handle DTD Declarations
The NSXMLParser class reports to its delegate DTD declarations it encounters in a document (assuming the
delegate implements the necessary methods). If the language schema you use is DTD, NSXMLParser helps you
acquire the data you need either for validation or for other purposes, such as enforcing correctness when
dynamically constructing objects (for example, a menu template).
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
25
Validation Tips and TechniquesThe DTD Delegation Methods
The NSXMLParser class defines a half dozen delegation methods that the parser invokes when the parser
encounters a DTD declaration in a internal or external source. These methods are of the form:
parser:foundTypeDeclarationWithName:...
The third parameter and any subsequent parameters depend on the type of declaration. The following list
briefly describes the NSXMLParser delegation methods related to DTD declarations.
- parser:foundElementDeclarationWithName:model:
Example:
- parser:foundAttributeDeclarationWithName:forElement:type:defaultValue:
Example:
- parser:foundInternalEntityDeclarationWithName:value:
Example:
- parser:foundExternalEntityDeclarationWithName:publicID:systemID:
Example:
- parser:foundNotationDeclarationWithName:publicID:systemID:
Example:
- parser:foundUnparsedEntityDeclarationWithName:publicID:systemID: notationName:
Example:
Resolving External DTD Entities
An XML document, in the DOCTYPE declaration that occurs near its beginning, often identifies an external DTD
file whose declarations prescribe its logical structure. For example, the following DOCTYPE declaration says
that the DTD related to the root element “addresses” can be located by the system identifier “addresses.dtd”.
Often the system identifier assumes a standard file-system location for DTDs—for example,
/System/Library/DTDs. At the start of processing, the NSXMLParser delegate is given an opportunity to
resolve this external entity and give the parser a list of DTD declarations to parse.
1. When you prepare the NSXMLParser instance,send it the setShouldResolveExternalEntities: with
an argument of YES.
2. Implement the delegation method parser:resolveExternalEntityName:systemID: to return the
declarations in the external DTD file as an NSData object.
Validation Tips and Techniques
Using NSXMLParser to Handle DTD Declarations
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
26If the DTD declarations are internal to an XML document, then the delegate will receive the DTD-declaration
messages automatically (assuming, of course, that it implements the related methods).
Constructing Rules for Elements
Just as elements are typically the most common kind of construct in an XML document, element declarations
are the most common kind of declaration in a DTD. They express rules for the composition of elements from
child elements, text, and other constituents.
An element declaration has three parts: the !ELEMENT keyword, the element name, and a content model. The
content model is everything after the name up to the terminating angle bracket. Consider the following
examples:
The content model can specify no content (EMPTY), any content (ANY, which israre), textual content (#PCDATA),
and child elements. It may identify child elements by name or by an entity reference (such as %plistObject;
in the third example above). The model can also specify mixed content—that is, the element can contain text
and child elements in any order. Through occurrence modifiers (*, +, ?) and other syntactical conventions, the
content model can also specify the order of child elements, whether an element is required or optional, how
many times an element may occur, and acceptable choices between elements. Occurrence modifiers can be
applied to groups of elements (in parentheses) as well as individual elements.
The job required for validation is to examine the content model of an element declaration and derive rules for
the composition of that element. As one approach, you might design classes for each type of rule as well as
for the scope of a rule (individual element or group of elements). You could then associate instances of that
rule class with an element through the name of the element. During validation the instances are queried with
regard to a current or potential member of an element.
Table 1 lists the most important rules derivable from an element declaration’s content model.
Table 1 Possible rules for element validation
Rule Sample content model Comments
Textual content only (#PCDATA)
Validation Tips and Techniques
Constructing Rules for Elements
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
27Rule Sample content model Comments
Vertical bars in this case have a meaning
different from choice; when #PCDATA is
present, they mean that text and child elements
can be intermixed.
(#PCDATA | bold |
italic)
Mixed content
No content EMPTY For flag-type values.
(name, address, Commas indicate prescribed sequence.
phone)
Required sequence
Without #PCDATA being a member (see Mixed
content), the vertical bars mean that one of the
listed elements must be used.
(read | write |
readwrite)
Choice
No modifier punctuation mark. Can apply to
individual element or group.
(name, address,
phone)
Occurs exactly once
Occurrence modifier is asterisk (“*”). Can apply
to individual element or group.
Occurs zero or more (%plistObject;)*
times
Occurrence modifier is plus sign (“+”). Can
apply to individual element or group.
Occurs one or more (property+)
times
Occurrence modifier is question mark (“?”). Can
apply to individual element or group.
Occurs zero or one (%implementation;?)
time
Constructing Rules for Attributes
Elements frequently have attributes associated with them, and consequently attribute-list declarations are
frequently encountered in DTDs. Attribute-list declarations specify the rules for attributes using a syntax that
is different from element declarations. They specify, in order, the associated element, the name of the attribute,
the type of the attribute, and a default value. For example, the declaration
states that the defaultIndex attribute, which is associated with the modifierMap element, is of type
NMTOKEN (meaning that it must be a valid XML name); the #REQUIRED keyword given as the default value
means that a value for the attribute must be supplied.
Validation Tips and Techniques
Constructing Rules for Attributes
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
28When a NSXMLParser instance encounters an attribute-list declaration, it sends
parser:foundAttributeDeclarationWithName:forElement:type:defaultValue: to its delegate.
Passed in as parameters are attribute name, the associated element, the attribute type, and its default value.
The rules for attributes derive from combinations of the last two parameter (type and default value). Table 2
lists some the possible rules that you can construct from attribute-list declarations.
Table 2 Possible rules for attribute validation
Type or Comments
default
Keywords or
example
Rule
The attribute value must be unique in the
XML document.
Unique value ID type
The value of the attribute must be
specified in the document.
Required value #REQUIRED default
Value must refer to valid ID-type value
elsewhere in document. IDREFS specifies
a list of ID references (in parentheses).
Refers to unique IDREF | IDREFS type
attribute value
Value must be valid XML name (including
entity references). NMTOKENS specifies a
list of XML names (in parentheses).
NMTOKEN | type
NMTOKENS
Valid XML name
Value is fixed #FIXED "value" default Value must be “value”.
Attribute enumeration: value must be one
of the XML names in parentheses.
(name | address type
| phone)
Valid XML name
in list
Attribute enumeration: value must be one
of the defined types in parentheses.
NOTATION (tiff type
| gif | jpg)
Valid defined type
in list
Handling Other Declarations
Other DTD declarationssuch asthose for entities and notations are less common than element and attribute-list
declarations. You can easily derive rule constructions for these other declarations after reviewing some DTD
documentation. However, there are a couple of things to keep in mind:
● You need to record entity declarations in case they are used as part of the content model for an element
declaration.
● Because notations can be made an attribute type, you should also keep track of them.
Validation Tips and Techniques
Handling Other Declarations
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
29This glossary defines some of the terms specific to XML, DTD, and related specifications and technologies. It
focuses primarily on terms that are part of the names of methods and constants declared by the NSXMLParser,
NSXMLNode, NSXMLDocument, NSXMLElement, NSXMLDTD, and NSXMLDTDNode classes.
atomic value
A value with a simple type as defined by the XML Schema standard. The types include string, decimal,
integer, float, double, Boolean, date, URI, array, and binary data. An XQuery query returns a sequence of
items that can contain one or more nodes or atomic values.
attribute
A property of an element expressed as a name-value pair. Attributes are used to encode data or provide
metadata that is associated with an element. In the following example,“version”isthe name of an attribute
of element plist and its value is "1.0":
attribute list declaration
Identifiesin a DTD an element that has attributes, the names of those attributes, what valuesthe attributes
may have, and default values. Example:
In this example, phone is the element name, location is the attribute name, (home | office |
mobile) is the allowable values, and home is the default value.
canonical
A form of an XML document in which it can be compared against another document for equivalence. If
two documents with differing physical representations have the same canonical form, they are considered
logically equivalent within the given application context. The canonical form of an XML document is
defined by the World Wide Web Consortium at http://www.w3.org/TR/xml-c14n.
CDATA block
A section of text that the parsershould pass uninterpreted to the client application. It appears as element
content. CDATA blocks are often used for code or data that contains “prohibited” characters, that is
characters of special syntactical significance to the parser (for example, “<“ and “&”). You can also use an
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
30
XML Glossaryentity reference to express any of these prohibited characters (for example, <) is a built-in entity
reference for specifying the “escaped” < character.
content model
The part of an element declaration that defines what the element may contain. A content model consists
of the names of child elements, #PCDATA (indicating text), entity references, or EMPTY (indicating an
empty elementsuch as ). Child elements and #PCDATA are enclosed within parentheses. Commas
between child elements specify that the elements must occur in the given sequence. The vertical-bar
character (“|”) instead of a comma indicates a logical OR relationship and can be used with #PCDATA.
Occurrence modifiers can be applied to individual elements or groups of elements:
●
“+” indicates the element or group can be repeated more than once but must occur at lease once.
●
“?” indicates the element or group is optional and may occur only once.
●
“*” indicates the element or group is optional and can occur more than once.
● No modifier indicates that the element or group must occur only once.
Examples of content models.
(#PCDATA)
(%plistObject)*
(lastName, middleInitial?, firstName, phone*)*
document order
The order of XML mark-up constructs as they appear in a document. When you send the NSXMLNode
messages nextNode (or previousNode) to each successive node object encountered in an NSXML tree,
you are traversing the tree forward (or backward) in document order.
DOM (Document Object Model)
An API for accessing and manipulating XML documents as tree structures. DOM derives from a World
Wide Web Consortium recommendation for a general object model for storing hierarchically structured
documents in memory.
DTD (Document Type Definition)
A way to define the legal elements and other building blocks of an XML document.
element
Markup tagsthat identify the nature of the content they surround. Elements have names and may contain
textual data, child elements, processing instructions, comments, and CDATA blocks. An element has a
single parent element, except for a document’s root element, which has no parent. An element may also
XML Glossary
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
31have attributes and namespace prefixes associated with it. Elements can also be empty (that is, without
content) and the developer can use them as flags.
The following is an example of an element with an attribute and mixed content (in this case, text, a child
element, and a CDATA block):
The following C++ code gives an example of how
cout
is used:
element declaration
Specifiesin a DTD the name of an element and what is permitted as content of the element. The declaration
may specify child elements, text, and entity references as content. It prescribesthe order of child elements
and (forsingle elements or for the entire group) whether it isrequired and whether it can appear multiple
times. Examples:
See also content model.
entity declaration
Associates in a DTD a name with some piece of XML content that is identified by an entity reference.
That content can be a literal value (such as identified by a character reference), a variable value specified
elsewhere in the DTD, orsome textual or binary value referenced in an external file. The last type of entity
is called an external entity. Examples:
entity and character reference
A reference in text to an externally or internally declared entity declaration. It must begin with an
ampersand and end with a semicolon. You can refer to entities that you declare elsewhere. There are five
predefined entities: “<“, “>”, “&”,single-quote character, and double-quote character. Character references
XML Glossary
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
32start with “” and are followed by numerical code points. Examples of references are ', >,
ç ; the first two are built-in entity references and the last is a character reference. See also unparsed
entity.
model
See content model.
namespace
A URI (Universal Resource Identifier) that qualifies an element or attribute name so as to avoid name
conflicts when a document contains XML from different sources. You declare a namespace in the start
tag of an element by appending a prefix to the predefined xmlns attribute (separated by a colon), and
then associating this with the value of the URI; for example:
Thereafter, you need only use a namespace prefix (“h” in the above example) with an element (separated
by a colon) to identify the element unambiguously. All child elements of the element with the namespace
declaration are associated with the same namespace through the prefix. The prefix-element name
combination (h:table from the example above) is called a qualified name. A namespace declaration
with no prefix after xmlns defines a default namespace, unlessthe value is an empty string, which means
“no namespace.” The URI in a namespace declaration doesn’t have to point to anything; it is just a
convenient way to get a unique name.
namespace prefix
A prefix defined in a namespace declaration to identify the namespace a particular element is associated
with. The namespace's qualified name (xmlns:localname ) appears only during output. All other operations,
such as those that get or set a namespace node’s value, use the local name only. See also namespace.
normalize
To coalesce all adjacent child text nodes into a single text node while removing empty text nodes.
Normalization is highly recommended before performing XPath and XQuery queries.
notation
Identifies by name the format either of an unparsed entity or an element bearing a specific notation
attribute; it can also identify the target of a processing instruction. A notation declaration gives a name
to the notation and an external identifier that enables a parser or its client to locate a helper application
that can process the data specified by the notation. Notations occur in attribute values, attribute-list
declarations, and entity declarations.
processing instruction
A construct that provides information to the application processing the XML document. The instructions
could instruct the application how, for example, to interpret the XML or display the results. Processing
instructions can occur within elements or at the top level of a document. The first word of the processing
instruction is called the target (its name) and every thing else is its object value. Example:
XML Glossary
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
33
qualified name
An element’s full name, consisting of prefix, colon, and local name. See also namespace.
sequence
A collection of items, each of which can be a node or an atomic value. XQuery queries return a sequence
(an NSArray in Cocoa), which may contain only a single item.
validation
A procedure that checks an XML document against the logical structure described by declarations in the
associated DTD (or other schema) to see if the XML conforms to it. Some of the constraints involved in
validation are proper element sequence and nesting, specification of required attributes, and correct
attribute type. For example, if an element is supposed to have one or more child elements but doesn’t,
the document containing the element is invalid. Before an XML document can be validated, it must first
be well-formed.
unparsed entity
An external resource referred to by entity reference whose contents may be binary data or text (including
non-XML text). Each unparsed entity has a notation associated with it.
well-formed
Refers to an XML document that obeys the syntax of XML. A parser cannot parse a document if its XML
is not well-formed. Some of the checks for whether a document is well-formed are:
● Element start tags must have end tags (except for empty elements).
● Attribute values must be quoted.
● Parameter entities must be declared before they are used.
● Markup constructs appear only where permitted.
XHTML
A more strictly prescribed version of HTML that makes it well-formed XML. XHTML is an official World
Wide Web Consortium recommendation.
XPath
An XML query language for locating nodes with an XML tree structure. It allowslocation paths, predicates,
and general expressions in queries. The Cocoa implementation uses XPath 2.0, which is a World Wide
Web Consortium recommendation. The NSXMLNode class enables XPath queries through its
nodesForXPath:error: method. (Note that the NSXML classes do not support deprecated XPath 1.0
features such as namespace axis.)
XML Glossary
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
34XQuery
A flexible and powerful XML query language that lets you compose logically complex queries using
operators, quantifiers, functions and FLOWR expressions (referring to the keywords for, let, order
by, where, and return). The NSXMLNode class enables XQuery 1.0 queries through its
objectsForXQuery:error: method
XSLT (Extensible Stylesheet Language Transformations)
An XML application for transforming an XML document into another XML document or into an HTML,
RTF, or plain-text document. The stylesheet used in a transformation has template rules, each consisting
of a pattern and a template. The NSXMLDocument class permits access to XSLT through its
objectByApplyingXSLT:error: and objectByApplyingXSLTAtURL:error: methods.
XML Glossary
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
35This table describes the changes to Event-Driven XML Programming Guide .
Date Notes
2010-03-24 Update example code to new initializer pattern.
2008-09-09 Added note about introduction of namespace support in v10.4.
2006-12-05 Added memory management guideline and corrected code examples.
Updated the glossary of XML terms. Changed title from "Event-Driven
XML Parsing." Changed "Rendezvous" to "Bonjour."
2005-04-29
Updated the XML glossary to define additional terms primarily related to
the NSXML set of classes. This glossary is shared with Tree-Based XML
Programming Guide .
2004-07-27 Minor bug fix.
2004-01-21 First version of Event-Driven XML Parsing .
2010-03-24 | © 2004, 2010 Apple Inc. All Rights Reserved.
36
Document Revision HistoryApple Inc.
© 2004, 2010 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Bonjour, Cocoa, Mac, and
OS X are trademarks of Apple Inc., registered in
the U.S. and other countries.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
Date and Time
Programming GuideContents
About Dates and Times 5
At a Glance 5
Creating and Using Date Objects to Represent Absolute Points in Time 5
Working with Calendars and Date Components 6
Performing Date and Time Calculations 6
Working with Different Time Zones 6
Special Considerations for Historical Dates 6
How to Use this Document 7
See Also 7
Dates 8
Date Fundamentals 8
Creating Date Objects 9
Basic Date Calculations 10
Calendars, Date Components, and Calendar Units 11
Calendar Basics 11
Date Components and Calendar Units 12
Converting between Dates and Date Components 12
Converting from One Calendar to Another 14
Calendrical Calculations 16
Adding Components to a Date 16
Determining Temporal Differences 18
Checking When a Date Falls 20
Week-Based Calendars 21
Using Time Zones 23
Creating Time Zones 23
Application Default Time Zone 24
Creating Dates with Time Zones 24
Time Zones and Daylight Saving Time 25
Historical Dates 26
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
2The Gregorian Calendar Has No Year 0 26
The Julian to Gregorian Transition 27
Working with Eras with Backward Time Flow 27
Document Revision History 29
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
3Tables and Listings
Dates 8
Listing 1 Creating dates with time intervals 9
Listing 2 Creating dates by adding a time interval 9
Calendars, Date Components, and Calendar Units 11
Listing 3 Creating calendar objects 11
Listing 4 Creating a date components object 12
Listing 5 Getting a date’s components 13
Listing 6 Creating a date from components 13
Listing 7 Creating a yearless date 14
Listing 8 Converting date components from one calendar to another 14
Calendrical Calculations 16
Table 1 December 2009 Calendar 21
Table 2 January 2010 Calendar 21
Listing 9 An hour and a half from now 16
Listing 10 Getting the Sunday in the current week 16
Listing 11 Getting the beginning of the week 17
Listing 12 Getting the difference between two dates 18
Listing 13 Days between two dates, as the number of midnights between 19
Listing 14 Days between two dates in different eras 19
Listing 15 Determining whether a date is this week 20
Using Time Zones 23
Listing 16 Creating a date from components using a specific time zone 24
Historical Dates 26
Listing 17 Using negative years to represent BC dates 26
Listing 18 Tomorrow in the BC era 27
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
4Date and time objects allow you to store references to particular instances in time. You can use date and time
objectsto perform calculations and comparisonsthat account for the corner cases of date and time calculations.
At a Glance
There are three main classes used for working with dates and times.
● NSDate allows you to represent an absolute point in time.
● NSCalendar allows you to represent a particular calendar, such as a Gregorian or Hebrew calendar. It
providesthe interface for most date-based calculations and allows you to convert between NSDate objects
and NSDateComponents objects.
● NSDateComponents allows you to represent the components of a particular date, such as hour, minute,
day, year, and so on.
In addition to these classes, NSTimeZone allows you to represent a geopolitical region’stime zone information.
It eases the task of working across different time zones and performing calculations that may be affected by
daylight savings time transitions.
Creating and Using Date Objects to Represent Absolute Points in Time
Date objects represent dates and times in Cocoa. Date objects allow you to store absolute points in time which
are meaningful across locales, calendars and timezones.
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
5
About Dates and TimesRelevant Chapters: “Dates” (page 8)
Working with Calendars and Date Components
Date components allow you to break a date down into the various parts that comprise it, such as day, month,
year, hour, and so on. Calendars represent a particular form of reckoning time, such as the Gregorian calendar
or the Chinese calendar. Calendar objects allow you to convert between date objects and date component
objects, as well as from one calendar to another.
Relevant Chapters: “Calendars, Date Components, and Calendar Units” (page 11)
Performing Date and Time Calculations
Calendars and date components allow you to perform calculationssuch asthe number of days or hours between
two dates or finding the Sunday in the current week. You can also add components to a date or check when
a date falls.
Relevant Chapters: “Calendrical Calculations” (page 16)
Working with Different Time Zones
Time zone objects allow you to present absolute times as local—that is, wall clock—time. In addition to time
offsets, they also keep track of daylight saving time differences. Proper use of time zone objects can avoid
issues such as miscalculation of elapsed time due to daylight saving time transitions or the user moving to a
different time zone.
Relevant Chapters: “Using Time Zones” (page 23)
Special Considerations for Historical Dates
Dates in the past have a number of edge cases that do not exist for contemporary dates. These include issues
such as datesthat do not exist in a particular calendar—such asthe lack of the year 0 in the Gregorian calendar—
or calendar transitions—such as the Julian to Gregorian transition in the Middle Ages. There are also eras with
seemingly backward time flow—such as BC dates in the Gregorian calendar.
About Dates and Times
At a Glance
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
6Relevant Chapters: “Historical Dates” (page 26)
How to Use this Document
If your application keeps track of dates and times, read from “Dates” (page 8) to “Using Time Zones” (page
23). The NSDate, NSCalendar, NSDateComponents, and NSTimeZone classes described in these chapters
work together to store, compare, and manipulate dates and times.
If your application deals with dates in the past—particularly prior to the early 1900s, also read “Historical
Dates” (page 26) to learn about some of the issues that can arise when dealing with dates in the past.
See Also
If you are new to Cocoa, read:
● Cocoa Fundamentals Guide , which introduces the basic concepts, terminology, architectures, and design
patterns of the Cocoa frameworks and development environment.
If you display dates and times to users or create dates from user input, read:
● Data Formatting Guide , which explains how to create and format user-readable strings from date objects,
and how to create date objects from formatted strings.
About Dates and Times
How to Use this Document
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
7Date objects allow you to represent dates and times in a way that can be used for date calculations and
conversions. As absolute points in time, date objects are meaningful across locales, timezones, and calendars.
Date Fundamentals
Cocoa represents dates and times as NSDate objects. NSDate is one of the fundamental Cocoa value objects.
A date object represents an invariant point in time. Because a date is a point in time, it implies clock time as
well as a day, so there is no way to define a date object to represent a day without a time.
To understand how Cocoa handles dates, you must consider NSCalendar and NSDateComponents objects
as well. In a nontechnical context, a point in time is usually represented by a combination of a clock time and
a day on a particular calendar (such as the Gregorian or Hebrew calendar). Supporting different calendars is
important for localization. In Cocoa, you use a particular calendar to decompose a date object into its date
components such as year, month, day, hour, and minute. Conversely, you can use a calendar to create a date
object from date components. Calendar and date component objects are described in more detail in “Calendars,
Date Components, and Calendar Units” (page 11).
NSDate provides methods for creating dates, comparing dates, and computing intervals. Date objects are
immutable. The standard unit of time for date objects is floating point value typed as NSTimeInterval and
is expressed in seconds. Thistype makes possible a wide and fine-grained range of date and time values, giving
precision within milliseconds for dates 10,000 years apart.
NSDate computes time as seconds relative to an absolute reference time: the first instant of January 1, 2001,
Greenwich Mean Time (GMT). Dates before then are stored as negative numbers; dates after then are stored
as positive numbers. The sole primitive method of NSDate, timeIntervalSinceReferenceDate provides
the basis for all the other methods in the NSDate interface. NSDate converts all date and time representations
to and from NSTimeInterval values that are relative to the absolute reference date.
Cocoa implementstime according to the Network Time Protocol (NTP)standard, which is based on Coordinated
Universal Time.
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
8
DatesCreating Date Objects
If you want a date that represents the current time, you allocate an NSDate object and initialize it with init:
NSDate *now = [[NSDate alloc] init];
or use the NSDate class method date to create the date object. If you want some time other than the current
time, you can use one of NSDate’s initWithTimeInterval... or dateWithTimeInterval... methods;
typically, however, you use a more sophisticated approach employing a calendar and date components as
described in “Calendar Basics” (page 11).
The initWithTimeInterval... methods initialize date objects relative to a particular time, which the
method name describes. You specify (in seconds) how much more recent or how much more in the past you
want your date object to be. To specify a date that occurs earlier than the method’s reference date, use a
negative number of seconds.
Listing 1 defines two date objects. The tomorrow object is exactly 24 hours from the current date and time,
and yesterday is exactly 24 hours earlier than the current date and time.
Listing 1 Creating dates with time intervals
NSTimeInterval secondsPerDay = 24 * 60 * 60;
NSDate *tomorrow = [[NSDate alloc]
initWithTimeIntervalSinceNow:secondsPerDay];
NSDate *yesterday = [[NSDate alloc]
initWithTimeIntervalSinceNow:-secondsPerDay];
[tomorrow release];
[yesterday release];
Listing 2 shows how to get new date objects with date-and-time values adjusted from existing date objects
using dateByAddingTimeInterval:.
Listing 2 Creating dates by adding a time interval
NSTimeInterval secondsPerDay = 24 * 60 * 60;
NSDate *today = [[NSDate alloc] init];
NSDate *tomorrow, *yesterday;
tomorrow = [today dateByAddingTimeInterval: secondsPerDay];
Dates
Creating Date Objects
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
9yesterday = [today dateByAddingTimeInterval: -secondsPerDay];
[today release];
Basic Date Calculations
To compare dates, you can use the isEqualToDate:, compare:, laterDate:, and earlierDate: methods.
These methods perform exact comparisons, which means they detect sub-second differences between dates.
You may want to compare dates with a less fine granularity. For example, you may want to consider two dates
equal if they are within a minute of each other. If this is the case, use timeIntervalSinceDate: to compare
the two dates. The following code fragmentshows how to use timeIntervalSinceDate: to see if two dates
are within one minute (60 seconds) of each other.
if (fabs([date2 timeIntervalSinceDate:date1]) < 60) ...
To obtain the difference between a date object and another point in time, send a timeIntervalSince...
message to the date object. For example, timeIntervalSinceNow gives you the time, in seconds, between
the current time and the receiving date object.
To get the component elements of a date, such as the day of the week, use an NSDateComponents object in
conjunction with an NSCalendar object. This technique is described in “Calendar Basics” (page 11).
Dates
Basic Date Calculations
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
10Calendar objects encapsulate information about systems of reckoning time in which the beginning, length,
and divisions of a year are defined. You use calendar objects to convert between absolute times and date
components such as years, days, or minutes.
Calendar Basics
NSCalendar provides an implementation of various calendars. It provides data for several different calendars,
including Buddhist, Gregorian, Hebrew, Islamic, and Japanese (which calendars are supported depends on the
release of the operating system—check the NSLocale class to determine which are supported on a given
release). NSCalendar is closely associated with the NSDateComponents class, instances of which describe
the component elements of a date required for calendrical computations.
Calendars are specified by constants in NSLocale. You can get the calendar for the user's preferred locale
most easily using the NSCalendar method currentCalendar; you can get the default calendar from any
NSLocale object using the key NSLocaleCalendar. You can also create an arbitrary calendar object by
specifying an identifier for the calendar you want. Listing 3 shows how to create a calendar object for the
Japanese calendar and for the current user.
Listing 3 Creating calendar objects
NSCalendar *currentCalendar = [NSCalendar currentCalendar];
NSCalendar *japaneseCalendar = [[NSCalendar alloc]
initWithCalendarIdentifier:NSJapaneseCalendar];
NSCalendar *usersCalendar =
[[NSLocale currentLocale] objectForKey:NSLocaleCalendar];
Here, usersCalendar and currentCalendar are equal, although they are different objects.
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
11
Calendars, Date Components, and Calendar UnitsDate Components and Calendar Units
You represent the component elements of a date—such as the year, day, and hour—using an
NSDateComponents object. An NSDateComponents object can hold either absolute values or quantities of
units (see “Adding Components to a Date” (page 16) for an example of using NSDateComponents to specify
quantities of units). For date components objects to be meaningful, you need to know the associated calendar
and purpose.
iOS Note: In iOS 4.0 and later, NSDateComponents objects can contain a calendar, a timezone, and
a date object. This allows date components to be passed to or returned from a method and retain
their meaning.
Day, week, weekday, month, and year numbers are generally 1-based, but there may be calendar-specific
exceptions. Ordinal numbers, where they occur, are 1-based. Some calendars may have to map their basic unit
concepts into the year/month/week/day/… nomenclature. The particular values of the unit are defined by
each calendar and are not necessarily consistent with values for that unit in another calendar.
Listing 4 shows how you can create a date components object that you can use to create the date where the
year unit is 2004, the month unit is 5, and the day unit is 6 (in the Gregorian calendar this is May 6th, 2004).
You can also use it to add 2004 year units, 5 month units, and 6 day units to an existing date. The value of
weekday is undefined since it is not otherwise specified.
Listing 4 Creating a date components object
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setDay:6];
[components setMonth:5];
[components setYear:2004];
NSInteger weekday = [components weekday]; // Undefined (== NSUndefinedDateComponent)
Converting between Dates and Date Components
To decompose a date into constituent components, you use the NSCalendar method
components:fromDate:. In addition to the date itself, you need to specify the components to be returned
in the NSDateComponents object. For this, the method takes a bit mask composed of Calendar Units
constants. There is no need to specify any more components than those in which you are interested. Listing
5 shows how to calculate today’s day and weekday.
Calendars, Date Components, and Calendar Units
Date Components and Calendar Units
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
12Listing 5 Getting a date’s components
NSDate *today = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *weekdayComponents =
[gregorian components:(NSDayCalendarUnit | NSWeekdayCalendarUnit)
fromDate:today];
NSInteger day = [weekdayComponents day];
NSInteger weekday = [weekdayComponents weekday];
This gives you the absolute components for a date. For example, if you ask for the year and day components
for November 7, 2010, you get 2010 for the year and 7 for the day. If you instead want to know what number
day of the year it is you can use the ordinalityOfUnit:inUnit:forDate: method of the NSCalendar
class.
It is also possible to create a date from components. You can configure an instance of NSDateComponents
to specify the components of a date and then use the NSCalendar method dateFromComponents: to create
the corresponding date object. You can provide as many components as you need (or choose to). When there
is incomplete information to compute an absolute time, default values such as 0 and 1 are usually chosen by
a calendar, but this is a calendar-specific choice. If you provide inconsistent information, calendar-specific
disambiguation is performed (which may involve ignoring one or more of the parameters).
Listing 6 shows how to create a date object to represent (in the Gregorian calendar) the first Monday in May,
2008.
Listing 6 Creating a date from components
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setWeekday:2]; // Monday
[components setWeekdayOrdinal:1]; // The first Monday in the month
[components setMonth:5]; // May
[components setYear:2008];
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *date = [gregorian dateFromComponents:components];
Calendars, Date Components, and Calendar Units
Converting between Dates and Date Components
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
13To guarantee correct behavior you must make sure that the components used make sense for the calendar.
Specifying “out of bounds” components—such as a day value of -6 or February 30th in the Gregorian
calendar—produce undefined behavior.
You may want to create a date object without components such as years—to store your friend’s birthday, for
instance. While it is not technically possible to create a yearless date, you can use date components to create
a date object without a specified year, as in Listing 7.
Listing 7 Creating a yearless date
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setMonth:11];
[components setDay:7];
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *birthday = [gregorian dateFromComponents:components];
Note that birthday in this instance has the default value for the year, which in this case is 1 AD (though it is
not guaranteed to always default to 1 AD). If you later convert this date back to components, or use an
NSDateFormatter object to display it, make sure to not use the year value (as your friend may not appreciate
being listed asthat old). You can use the NSDateFormatter dateFormatFromTemplate:options:locale:
method to create a yearless date formatter that adjusts to the users locale. For more information on date
formatting see Data Formatting Guide .
Converting from One Calendar to Another
To convert components of a date from one calendar to another—for example, from the Gregorian calendar
to the Hebrew calendar—you first create a date object from the components using the first calendar, then you
decompose the date into components using the second calendar. Listing 8 shows how to convert date
components from one calendar to another.
Listing 8 Converting date components from one calendar to another
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setDay:6];
[comps setMonth:5];
[comps setYear:2004];
Calendars, Date Components, and Calendar Units
Converting from One Calendar to Another
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
14NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDate *date = [gregorian dateFromComponents:comps];
[comps release];
[gregorian release];
NSCalendar *hebrew = [[NSCalendar alloc]
initWithCalendarIdentifier:NSHebrewCalendar];
NSUInteger unitFlags = NSDayCalendarUnit | NSMonthCalendarUnit |
NSYearCalendarUnit;
NSDateComponents *components = [hebrew components:unitFlags fromDate:date];
NSInteger day = [components day]; // 15
NSInteger month = [components month]; // 9
NSInteger year = [components year]; // 5764
Calendars, Date Components, and Calendar Units
Converting from One Calendar to Another
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
15NSDate providesthe absolute scale and epoch for dates and times, which can then be rendered into a particular
calendar for calendrical calculations or user display. To perform calendrical calculations, you typically need to
get the component elements of a date, such as the year, the month, and the day. You should use the provided
methods for dealing with calendrical calculations because they take into account corner cases like daylight
savings time starting or ending and leap years.
Adding Components to a Date
You use the dateByAddingComponents:toDate:options: method to add components of a date (such
as hours or months) to an existing date. You can provide as many components as you wish. Listing 9 shows
how to calculate a date an hour and a half in the future.
Listing 9 An hour and a half from now
NSDate *today = [[NSDate alloc] init];
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
[offsetComponents setHour:1];
[offsetComponents setMinute:30];
// Calculate when, according to Tom Lehrer, World War III will end
NSDate *endOfWorldWar3 = [gregorian dateByAddingComponents:offsetComponents
toDate:today options:0];
Components to add can be negative. Listing 10 shows how you can get the Sunday in the current week (using
a Gregorian calendar).
Listing 10 Getting the Sunday in the current week
NSDate *today = [[NSDate alloc] init];
NSCalendar *gregorian = [[NSCalendar alloc]
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
16
Calendrical CalculationsinitWithCalendarIdentifier:NSGregorianCalendar];
// Get the weekday component of the current date
NSDateComponents *weekdayComponents = [gregorian components:NSWeekdayCalendarUnit
fromDate:today];
/*
Create a date components to represent the number of days to subtract from the
current date.
The weekday value for Sunday in the Gregorian calendar is 1, so subtract 1 from
the number of days to subtract from the date in question. (If today is Sunday,
subtract 0 days.)
*/
NSDateComponents *componentsToSubtract = [[NSDateComponents alloc] init];
[componentsToSubtract setDay: 0 - ([weekdayComponents weekday] - 1)];
NSDate *beginningOfWeek = [gregorian dateByAddingComponents:componentsToSubtract
toDate:today options:0];
/*
Optional step:
beginningOfWeek now has the same hour, minute, and second as the original date
(today).
To normalize to midnight, extract the year, month, and day components and create
a new date from those components.
*/
NSDateComponents *components =
[gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit |
NSDayCalendarUnit) fromDate: beginningOfWeek];
beginningOfWeek = [gregorian dateFromComponents:components];
Sunday is not the beginning of the week in all locales. Listing 11 illustrates how you can calculate the first
moment of the week (as defined by the calendar's locale):
Listing 11 Getting the beginning of the week
NSDate *today = [[NSDate alloc] init];
Calendrical Calculations
Adding Components to a Date
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
17NSDate *beginningOfWeek = nil;
BOOL ok = [gregorian rangeOfUnit:NSWeekCalendarUnit startDate:&beginningOfWeek
interval:NULL forDate: today];
Determining Temporal Differences
There are a few ways to calculate the amount of time between dates. Depending on the context in which the
calculation is made, the user likely expects different behavior. Whichever calculation you use, it should be clear
to the user how the calculation is being performed. Since Cocoa implementstime according to the NTP standard,
these methods ignore leap seconds in the calculation. You use components:fromDate:toDate:options:
to determine the temporal difference between two dates in units other than seconds (which you can calculate
with the NSDate method timeIntervalSinceDate:). Listing 12 shows how to get the number of months
and days between two dates using a Gregorian calendar.
Listing 12 Getting the difference between two dates
NSDate *startDate = ...;
NSDate *endDate = ...;
NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSUInteger unitFlags = NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents *components = [gregorian components:unitFlags
fromDate:startDate
toDate:endDate options:0];
NSInteger months = [components month];
NSInteger days = [components day];
This method handles overflow as you may expect. If the fromDate: and toDate: parameters are a year and
3 days apart and you ask for only the days between, it returns an NSDateComponents object with a value of
368 (or 369 in a leap year) for the day component. However, this method truncatesthe results of the calculation
to the smallest unit supplied. For instance, if the fromDate: parameter corresponds to Jan 14, 2010 at 11:30
PM and the toDate: parameter corresponds to Jan 15, 2010 at 8:00 AM, there are only 8.5 hours between the
two dates. If you ask for the number of days, you get 0, because 8.5 hours is less than 1 day. There may be
Calendrical Calculations
Determining Temporal Differences
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
18situations where this should be 1 day. You have to decide which behavior your users expect in a particular
case. If you do need to have a calculation that returns the number of days, calculated by the number of
midnights between the two dates, you can use a category to NSCalendar similar to the one in Listing 13.
Listing 13 Days between two dates, as the number of midnights between
@implementation NSCalendar (MySpecialCalculations)
-(NSInteger)daysWithinEraFromDate:(NSDate *) startDate toDate:(NSDate *) endDate
{
NSInteger startDay=[self ordinalityOfUnit:NSDayCalendarUnit
inUnit: NSEraCalendarUnit forDate:startDate];
NSInteger endDay=[self ordinalityOfUnit:NSDayCalendarUnit
inUnit: NSEraCalendarUnit forDate:endDate];
return endDay-startDay;
}
@end
This approach works for other calendar units by specifying a different NSCalendarUnit value for the
ordinalityOfUnit: parameter. For example, you can calculate the number of years based on the number
of times Jan 1, 12:00 AM is present between.
Do not use this method for comparing second differences because it overflows NSInteger on 32-bit platforms.
This method is only valid if you stay within the same era (in the Gregorian Calendar this means that both dates
must be AD or both must be BC). If you do need to compare dates across an era boundary you can use something
similar to the category in Listing 14.
Listing 14 Days between two dates in different eras
@implementation NSCalendar (MyOtherMethod)
-(NSInteger) daysFromDate:(NSDate *) startDate toDate:(NSDate *) endDate
{
NSCalendarUnit units=NSEraCalendarUnit | NSYearCalendarUnit |
NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents *comp1=[self components:units fromDate:startDate];
NSDateComponents *comp2=[self components:units fromDate endDate];
[comp1 setHour:12];
[comp2 setHour:12];
NSDate *date1=[self dateFromComponents: comp1];
Calendrical Calculations
Determining Temporal Differences
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
19NSDate *date2=[self dateFromComponents: comp2];
return [[self components:NSDayCalendarUnit fromDate:date1 toDate:date2
options:0] day];
}
@end
This method creates components from the given dates, and then normalizes the time and compares the two
dates. This calculation is more expensive than comparing dates within an era. If you do not need to cross era
boundaries use the technique shown in Listing 13 (page 19) instead.
Checking When a Date Falls
If you need to determine if a date falls within the current week (or any unit for that matter) you can make use
of the NSCalendar method rangeOfUnit:startDate:interval:forDate:. Listing 15 shows a method
that determines if a given date falls within this week. The week in this case is defined as the period between
Sunday at midnight to the following Saturday just before midnight (in the Gregorian calendar).
Listing 15 Determining whether a date is this week
-(BOOL)isDateThisWeek:(NSDate *)date {
NSDate *start;
NSTimeInterval extends;
NSCalendar *cal=[NSCalendar autoupdatingCurrentCalendar];
NSDate *today=[NSDate date];
BOOL success= [cal rangeOfUnit:NSWeekCalendarUnit startDate:&start
interval: &extends forDate:today];
if(!success)return NO;
NSTimeInterval dateInSecs = [date timeIntervalSinceReferenceDate];
NSTimeInterval dayStartInSecs= [start timeIntervalSinceReferenceDate];
if(dateInSecs > dayStartInSecs && dateInSecs < (dayStartInSecs+extends)){
return YES;
}
else {
return NO;
}
Calendrical Calculations
Checking When a Date Falls
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
20}
This code uses NSTimeInterval values for the date to test and the start of the week and uses those to
determine whether the date is this week.
Week-Based Calendars
A week-based calendar is defined by the weeks of a year. However, this can be complicated when the first
week of the calendar overlapsthe last week of the previous year’s calendar. In this case there are two important
properties of the calendar:
1. What is the first day of the week?
2. How many days does a week near the beginning of the year have to have within the ordinary calendar
year for it to be considered the first week in the week-based calendar year?
A week-based calendar's first day of the year is on the first day of the week. The first week is preferred to be
the week containing Jan 1 if that week satisfies the defined answer for the second point above.
For example, suppose the first day of the week is defined as Monday, in a week-based calendar interpretation
of the Gregorian calendar. Consider the 2009/2010 transition shown in Table 1 and Table 2:
Table 1 December 2009 Calendar
Sunday Monday Tuesday Wednesday Thursday Friday Saturday
20 21 22 23 24 25 26
27 28 29 30 31
Table 2 January 2010 Calendar
Sunday Monday Tuesday Wednesday Thursday Friday Saturday
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
Since the first day of the week is Monday, the 2010 week-based calendar year can begin either December 28
or January 4. That is, December 30, 2009 (ordinary) could be December 30, 2010 (week-based).
Calendrical Calculations
Week-Based Calendars
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
21To choose between these two possibilities, there is the second criterion. Week Dec 28 - Jan 3 has 3 days in
2010. Week Jan 4-Jan 10 has 7 days in 2010.
If the minimum number of days in a first week is defined as 1 or 2 or 3, the week of Dec 28 satisfies the first
week criteria and would be week 1 of the week-based calendar year 2010. Otherwise, the week of Jan 4 is the
first week.
As another example,suppose you wanted to define your week-based calendarscheme such that the first week
of the week-based calendar year is the week beginning with the first occurrence of the first day of the week
in the ordinary calendar year. Another way to put that is that you always want the first week of the week-based
calendar year to be within the new ordinary calendar year, you never want your week-based calendar to start
back in December of the previous ordinary year as discussed in the previous example. Or, you always want
your week based calendar to start on Jan 1 or later.
In Table 2 (page 21) Monday January 4 is the first Monday of the ordinary year, so the week-based calendar
begins on that day. What you are requesting then is that the first week of your week-based calendar is entirely
within the new ordinary year or that the minimum number of days in first week is 7.
The NSYearForWeekOfYearCalendarUnit is the year number of a week-based calendar interpretation of
the calendar you're working with, where the two properties of the week-based calendar discussed in above
correspond to these two NSCalendar properties: firstWeekday and minimumDaysInFirstWeek.
Calendrical Calculations
Week-Based Calendars
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
22Time zones can create numerous problems for applications. Consider the following situation. You are in New
York and it is 12:30 AM. You have an application that displays all of the Major League Baseball games that
happen tomorrow. Because tomorrow is different depending on the time zone, situations like this must be
carefully accounted for. Fortunately, a little planning and the assistance of the NSTimeZone class ease this
task considerably.
NSTimeZone is an abstract class that defines the behavior of time zone objects. Time zone objects represent
geopolitical regions. Consequently, these objects have region names. Time zone objects also represent a
temporal offset, either plus or minus, from Greenwich Mean Time (GMT) and an abbreviation (such as PST).
Creating Time Zones
Time zones affect the values of date components that are calculated by calendar objects for a given NSDate
object. You can create an NSTimeZone object and use it to set the time zone of an NSCalendar object. By
default, NSCalendar uses the default time zone for the application—or process—when the calendar object
is created. Unless the default time zone has been otherwise set, it is the time zone set in System Preferences.
In most cases, the user’s default time zone should be used when creating date objects. There are cases when
it may be necessary to use arbitrary time zones. For example, the user may want to specify that an appointment
is in Greenwich Mean Time, because it is during her business trip to London next week. NSTimeZone provides
several class methods to make time zone objects: timeZoneWithName:, timeZoneWithAbbreviation:,
and timeZoneForSecondsFromGMT:. In most cases timeZoneWithName: provides the most accurate time
zone, as it adjusts for daylight saving time, the trade-off is that you must know more precisely the location you
are creating a time zone for.
For a complete list of time zone names known to the system, you can use the knownTimeZoneNames class
method:
NSArray *timeZoneNames = [NSTimeZone knownTimeZoneNames];
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
23
Using Time ZonesApplication Default Time Zone
You can set the default time zone within your application using setDefaultTimeZone:. You can access this
default time zone at any time with the defaultTimeZone class method. With the localTimeZone class
method you can get a time zone object that automatically updates itself to reflect changes to the default time
zone.
Creating Dates with Time Zones
Time zones play an important part in determining when datestake place. Consider a simple calendar application
that keeps track of appointments. For example, say you live in Chicago and you have a dentist appointment
coming up at 10:00 AM on Tuesday. You will be in New York for Sunday and Monday, however. When you
created that appointment it was done with the mindset of an absolute time. That time is 10:00 AM Central
Time; when you go to New York, the time should be presented as 11:00 AM because you are in a different time
zone, but it isthe same absolute time. On the other hand, if you create an appointment to wake up and exercise
every morning at 7:00 AM, you do not want your alarm to go off at 1:00 PM simply because you are on a
business trip to Dublin—or at 5:00 AM because you are in Los Angeles.
NSDate objects store dates in absolute time. For example, the date object created in Listing 16 represents
4:00 PM CDT, 5:00 EDT, and so on.
Listing 16 Creating a date from components using a specific time zone
NSCalendar *gregorian=[[NSCalendar alloc] initWithCalendarIdentifier:
NSGregorianCalendar];
[gregorian setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"CDT"]];
NSDateComponents *timeZoneComps=[[NSDateComponents alloc] init];
[timeZoneComps setHour:16];
//specify whatever day, month, and year is appropriate
NSDate *date=[gregorian dateFromComponents:timeZoneComps];
If you need to create a date that isindependent of timezone, you can store the date as an NSDateComponents
object—as long as you store some reference to the corresponding calendar.
In iOS, NSDateComponents objects can contain a calendar, a timezone, and a date object. You can therefore
store the calendar along with the components. If you use the date method of the NSDateComponents class
to access the date, make sure that the associated timezone is up-to-date.
Using Time Zones
Application Default Time Zone
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
24Time Zones and Daylight Saving Time
The NSTimeZone class also provides a number of instance methods to determine information about daylight
saving time:
● isDaylightSavingTime determines whether daylight saving time is currently in effect.
● daylightSavingTimeOffset determines the current daylight saving time offset. For most time zones
this is either zero or one.
● nextDaylightSavingTimeTransition determines when the next daylight saving time transition
occurs.
There are also similarly named methods for determining this information for specific dates. If you are keeping
track of events and appointments in your application, you can use this information to remind the user of
upcoming daylight saving time transitions.
Using Time Zones
Time Zones and Daylight Saving Time
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
25There are a number of issues that can arise when dealing with dates in the past that do not exist for
contemporary dates. These include dates that do not exist, previous eras where time flow moves from higher
year numbers to lower ones (such as BC dates in the Gregorian calendar), and calendar transitions (such as the
transition from the Julian calendar to the Gregorian calendar).
The Gregorian Calendar Has No Year 0
In the Julian and Gregorian calendars represented by the NSGregorianCalendar, there is no year 0. This
means that the day following December 31, 1 BC is January 1, 1 AD. All of the provided methods for calendrical
calculations take this into account, but you may need to account for it when you are creating dates from
components. If you do attempt to create a date with year 0, it is instead 1 BC. In addition, if you create a date
from components using a negative year value, it is created using astronomical year numbering in which 0
corresponds to 1 BC, -1 corresponds to 2 BC, and so on. For example, the two dates created in Listing
17equivalently represent May 7, 8 BC.
Listing 17 Using negative years to represent BC dates
NSCalendar *gregorian=[[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *bcDateComp=[[NSDateComponents alloc] init];
[bcDate setMonth: 5];
[bcDate setDay: 7];
[bcDate setYear: 8];
[bcDate setEra: 0];
NSDateComponents *astronDateComp=[[NSDateComponents alloc] init];
[bcDate setMonth: 5];
[bcDate setDay: 7];
[bcDate setYear: -7];
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
26
Historical DatesNSDate *bcDate=[gregorian dateFromComponents:bcDateComp];
NSDate *astronDate=[gregorian dateFromComponents:astronDateComp];
The Julian to Gregorian Transition
NSCalendar modelsthe transition from the Julian to Gregorian calendar in October 1582. During thistransition,
10 days were skipped. This means that October 15, 1582 follows October 4, 1582. All of the provided methods
for calendrical calculations take this into account, but you may need to account for it when you are creating
dates from components. Dates created in the gap are pushed forward by 10 days. For example October 8, 1582
is stored as October 18, 1582.
Some countries adopted the Gregorian calendar at variouslater times. Nevertheless, for consistency the change
is modeled at the same time regardless of locale. If you need absolute historical accuracy for a particular locale,
you can subtract the appropriate number of days from the date given by the Gregorian calendar. The number
of days to subtract corresponds to the number of extra leap days in the Julian calendar. Thus for every 100th
year, the Julian calendar falls behind a day if that year is not a multiple of 400. If you need to create a Julian
date, you must subtract the correct number of days from a Gregorian date (10 in the 1500s and 1600s, 11 in
the 1700s, 12 in the 1800s, 13 in the 1900s and 2000s, and so on). You must also take into account the existence
of leap days that aren’t in the Gregorian calendar.
Working with Eras with Backward Time Flow
In the Gregorian calendar, time is divided into two eras, the BC era and the AD era. In the BC era, time flows
in a direction seemingly backwards, that is from higher year numbers to lower. However, days and months
flow in the normal direction. For example February 1 follows January 31. This can be confusing if you ask what
day follows December 31, 7 BC. The correct answer is January 1, 6 BC. This example is illustrated in Listing 18.
Listing 18 Tomorrow in the BC era
NSCalendar *gregorian=[[NSCalendar alloc]
initWithCalendarIdentifier: NSGregorianCalendar];
NSDateComponents *dateBCComps=[[NSDateComponents alloc] init];
[dateBCComps setEra:0]; //Era 0 corresponds to BC
[dateBCComps setMonth:12];
[dateBCComps setDay:31];
[dateBCComps setYear:7];
Historical Dates
The Julian to Gregorian Transition
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
27NSDate *dateBC=[gregorian dateFromComponents:dateBCComps];
NSDateComponents *offsetDate=[[NSDateComponents alloc] init];
[offsetDate setDay:1];
NSDate *dateBC2=[gregorian dateByAddingComponents: offsetDate toDate:dateBC
options:0];
After this code executes dateBC2 corresponds to January 1, 6 BC.
Historical Dates
Working with Eras with Backward Time Flow
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
28This table describes the changes to Date and Time Programming Guide .
Date Notes
Expanded Calendrical Calculationssection. Added Historical Dates Section
and Week-Based Year Section.
2011-06-06
2010-02-24 Corrected code snippet.
2009-07-21 Added links to Cocoa Core Competencies.
Moved information about NSCalendarDate to an appendix, rewrote articles
to replace references to NSCalendarDate, and expanded content.
2008-07-03
Added a section about how to get date components using NSCalendar
and NSDateComponents and a section about how to convert from one
calendar to another.
Removed information about converting a date to a string. See
NSDateFormatter Class Reference for that information.
2007-09-04 Enhanced discussion of calendrical calculations using NSDateComponents.
2007-03-06 Added note regarding Julian and Gregorian calendars.
Corrected typographical errors. Added a note about the use of width
specifiers for calendar date format strings.
2006-05-23
Updated to include NSCalendar and NSDateFormatter changesintroduced
in OS X v10.4.
2006-02-07
2005-08-11 Changed title from "Dates and Times." Corrected minor typographic error.
Revision history was added to existing document. It will be used to record
changes to the content of the document.
2002-11-12
2011-06-06 | © 2002, 2011 Apple Inc. All Rights Reserved.
29
Document Revision HistoryApple Inc.
© 2002, 2011 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Chicago, Cocoa, Mac, New
York, Numbers, andOS X are trademarks of Apple
Inc., registered in the U.S. and other countries.
Times is a registered trademark of Heidelberger
Druckmaschinen AG, available from Linotype
Library GmbH.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
Window Programming
GuideContents
Introduction 6
Organization of This Document 6
See Also 8
How Windows Work 9
How a Window is Displayed 11
How Modal Windows Work 12
How Panels Work 14
How Window Controllers Work 15
Window Closing Behavior 16
Opening and Closing Windows 17
Window Layering and Types of Windows 18
Window Layering 18
Key and Main Windows 19
The Key Window 20
The Main Window 20
Changing a Window’s Status 21
Window Layers and Levels 22
Window Levels 22
Setting Ordering and Level Programmatically 22
Setting Window Collection Behavior 24
Spaces Collection Behavior 24
Exposé Collection Behavior 24
Window Cycling Behavior 25
Sizing and Placing Windows 26
Setting a Window’s Size and Location 26
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
2Window Cascading 27
Window Zooming 27
Constraining a Window’s Size and Location 27
Saving a Window’s Position into the User’s Defaults 29
Minimizing Windows 30
Using the Window Menu 31
Setting a Window’s Appearance 32
Setting a Window’s Style 32
Setting a Window’s Color and Transparency 33
Setting a Window’s Color Space 33
Setting a Window’s Content Border Thickness 33
Setting a Window’s Title and Represented File 34
Setting Attributes for the Window’s Image 35
Specifying How To Store the Window’s Image 35
Specifying Where To Store the Window’s Image 36
Specifying When the Window’s Image Is Created 36
Specifying Whether the Window’s Image Persists When Offscreen 37
Specifying the Depth Limit for the Window’s Image 37
Specifying Whether the Depth Limit Changes to the Screen’s Capacity 37
Specifying Whether Window Content Can Be Read or Written by Another Process 37
Handling Events in Windows 38
Using Keyboard Interface Control in Windows 39
Using the Window’s Field Editor 40
Using Window Notifications and Delegate Methods 41
Dragging Images to and from Windows 42
Updating the Cursor Image in a Window 43
Caching Window Images 44
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
3Document Revision History 45
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
4Figures and Listings
Window Layering and Types of Windows 18
Figure 1 Main, key, and inactive windows 19
Saving a Window’s Position into the User’s Defaults 29
Listing 1 Saving a window’s frame automatically 29
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
5An application displays windows on the screen that must be managed and coordinated. A window object
corresponds to at most one on-screen window. The two principal functions of windows are to provide an area
in which views can be placed and to accept and distribute events the user sends through actions with the
mouse and keyboard. The term window sometimes refers to the Application Kit object and sometimes to the
window server’s window device; which meaning is intended is made clear in context. Panels are a special kind
of window, typically serving an auxiliary function in an application, such as utility windows.
This document is intended for Cocoa developers who need to work with windows and panels in their
applications.
Organization of This Document
This programming topic describes how to use windows and panels. These articles give you basic information
on the different types of windows and how they work:
●
“How Windows Work” (page 9) describes the classes that define objects that manage and coordinate
the windows an application displays.
●
“How a Window is Displayed” (page 11) describes how window drawing is accomplished.
●
“How Modal Windows Work” (page 12) describes the behavior of modal windows.
●
“How Panels Work” (page 14) describes the various uses of panels.
●
“How Window Controllers Work” (page 15) describesthe relationship between a window and its controller.
●
“Window Layering and Types of Windows” (page 18) describes window layering and the concepts of key
and main windows, and how a window can avoid becoming key or main.
●
“Window Layers and Levels” (page 22) describes window levels, and how to place a window in a specific
level, such as the level for document windows, palettes, or tear-off menus.
●
“Setting Window Collection Behavior” (page 24) describes how to set a window’s behavior with Spaces,
Exposé, and window cycles.
These articles describe how to use windows:
●
“Opening and Closing Windows” (page 17) describes how to open and close, or just show and hide, a
window.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
6
Introduction●
“Sizing and Placing Windows” (page 26) describes how to control a window’s size and position, including
how to set its minimum and maximum size, how to constrain it to the screen, how to cascade it so its title
bar remains visible, how to zoom it as though the user pressed the zoom button, and how to center it on
the screen.
●
“Saving a Window’s Position into the User’s Defaults” (page 29) describes how to store a window’s position
in the user defaults system, so that it appears in the same location the next time the user starts the
application.
●
“Minimizing Windows” (page 30) describes how to replace a window with a smaller counterpart in the
Dock.
●
“Using the Window Menu” (page 31) describes how to place a window’s name in the Windows menu that
appears in most Cocoa applications.
These articles describe how to change what a window looks like:
●
“Setting a Window’s Appearance” (page 32) describes how to choose whether to display a window’s
peripheral elements, including its title bar, close box, zoom box, or size box. It also describes how to set
a window’s background color and transparency,
●
“Setting a Window’s Title and Represented File” (page 34) describes how to set a window’s title with either
a string or the filename of the window’s represented file.
●
“Setting Attributes for the Window’s Image” (page 35) describes how to set attributes for the window’s
device, which stores the window’s image, including how the image is stored, when the image is created,
and the image’s color depth.
These articles describe how to handle a window’s events:
●
“Handling Events in Windows” (page 38) gives basic information on how a window handles events.
●
“Using Keyboard Interface Control in Windows” (page 39) describes how to navigate between a window’s
fields using the Tab key and how to use the Return and Escape keys to select default buttons.
●
“Using the Window’s Field Editor” (page 40) describes how to use the window’s text object, which is
shared for light editing tasks.
These articles describe some advanced features of windows:
●
“Using Window Notifications and Delegate Methods” (page 41) describes the notifications and delegate
methods used when a window gains or loses key or main window status, minimizes, moves or resizes,
becomes exposed, or closes.
●
“Dragging Images to and from Windows” (page 42) describes what happens when the user wants to drag
an object into or out of a window.
Introduction
Organization of This Document
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
7●
“Updating the Cursor Image in a Window” (page 43) directs you to information on how to change the
cursor image when the cursor is over a specified area in a view.
●
“Caching Window Images” (page 44) describes how to temporarily cache a portion of a window’s image
so that it can be restored later. Thisis useful when highly dynamic drawing must be done over an otherwise
static image of the window.
See Also
For additional information on specific types of windows and panels, you can also see the following programming
topics:
● Sheet Programming Topics describes a dialog attached to a specific window, ensuring that a user never
loses track of which window the dialog belongs to.
● Drawer Programming Topics describes a type of view that slides out from one side of a window.
● Toolbar Programming Topics for Cocoa describes a standard way to display a toolbar for a titled window
below its title bar and provide users with a way to customize toolbars and save those customizations.
● Dialogs and Special Panels describes alert panels and other specialized types of panels, such as Font, Save,
and Print panels.
● Document-Based App Programming Guide for Mac describes how to use the architecture supplied by
AppKit to create applications that can create, open, load, and save multiple document files.
● Cocoa Event Handling Guide discusses the variety of ways your application objects can handle the events
they receive.
Introduction
See Also
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
8The NSWindow class defines objects that manage and coordinate the windows an application displays on the
screen. A single NSWindow object corresponds to at most one onscreen window. The two principal functions
of an NSWindow object are to provide an area in which NSView objects can be placed and to accept and
distribute, to the appropriate views, events the user instigates through actions with the mouse and keyboard.
Note that the term window sometimes refers to the Application Kit object and sometimes to the window
server’s display device; which meaning is intended is made clear in context. AppKit also defines an abstract
subclass of NSWindow—NSPanel—that adds behavior more appropriate for auxiliary windows.
An NSWindow object is defined by a frame rectangle that encloses the entire window, including its title bar,
border, and other peripheral elements (such as the resize control), and by a content rectangle that encloses
just its content area. Both rectangles are specified in the screen coordinate system and are restricted to integer
values. The frame rectangle establishesthe window’s base coordinate system. This coordinate system is always
aligned with and measured in the same increments as the screen coordinate system (in other words, the base
coordinate system can’t be rotated or scaled). The origin of the base coordinate system is the bottom-left
corner of the window’s frame rectangle.
Typically, you create windows using Interface Builder, which allows you to position them, set many of their
attributes, and lay out their views. The programmatic work you do with windows more often involves bringing
them on and off the screen; changing dynamic attributes such as the window’s title; running modal windows
to restrict user input; and assigning a delegate that can monitor certain of the window’s actions,such as closing,
zooming, and resizing.
You can also create a window programmatically with one of itsinitializers by specifying, among other attributes,
the size and location of its content rectangle. The frame rectangle is derived from the dimensions of the content
rectangle.
When it’s created, a window automatically createstwo views: an opaque frame view that fillsthe frame rectangle
and draws the border, title bar, other peripheral elements, and background, and a transparent content view
that fills the content rectangle. The frame view and its peripheral elements are private objects that your
application can’t access directly. The content view is the “highest” accessible view in the window; you can
replace the default content view with a view of your own creation using the setContentView: method. The
window determines the placement of the content view; you can’t position it using the NSView methods that
begin with setFrame; you must use the NSWindow class’s placement methods, as described in “Opening and
Closing Windows” (page 17).
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
9
How Windows WorkYou add other views to the window as subviews of the content view or as subviews of any of the content
view’s subviews, and so on, via the addSubview: method of NSView. This tree of views is called the window’s
view hierarchy. When a window is told to display itself, it does so by sending display... messages to the
top-level view in its view hierarchy. Because displaying is carried out in a determined order, the content view
(which is drawn first) may be wholly or partially obscured by itssubviews, and these subviews may be obscured
by their subviews (and so on).
How Windows Work
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
10Displaying an NSWindow object begins with the drawing performed by its view objects, which accumulates
in the window’s display buffer or appears immediately on the screen. Windows, like NSView objects, can be
displayed unconditionally or merely marked as needing display, using the display and
setViewsNeedDisplay: methods, respectively. A displayIfNeeded message causes the window’s views
to display only if they’ve been marked as needing display. Normally, any time a view is marked as needing
display, the window makes note of this fact and automatically displays itself shortly thereafter. This automatic
display is typically performed on each pass through the event loop, but can be turned off using the
setAutodisplay: method. If you turn off autodisplay for a window, you’re then responsible for displaying
it whenever necessary.
A window’s views can be drawn concurrently. You can use the methods allowsConcurrentViewDrawing
and setAllowsConcurrentViewDrawing: to determine and set, respectively, whether or not a window
draws its views concurrently. By default, a window’s views are drawn concurrently.
On each passthrough the event loop, the application object invokesits updateWindows method, which sends
an update message to each window. Subclasses of NSWindow can override this method to examine the state
of the application and change their own state or appearance accordingly—enabling or disabling menus,
buttons, and other controls based on the object that’s selected, for example.
In addition to displaying itself on the screen, a window can print itself in its entirety, just as a view can. The
print: method runs the application’s Print panel and causes the window’s frame view to print itself.
dataWithEPSInsideRect: behaves similarly. For additional information see Printing Programming Guide
for OS X .
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
11
How a Window is DisplayedYou can make a whole window or panel run in application-modal fashion, using the application’s normal event
loop machinery but restricting input to the modal window or panel. Modal operation is useful for windows
and panels that require the user’s attention before an action can proceed. Examples include error messages
and warnings, as well as operations that require input, such as open dialogs, or dialogs that apply to multiple
windows.
There are two mechanisms for operating an application-modal window or panel. The first, and simpler, is to
invoke the runModalForWindow: method of NSApplication, which monopolizes events for the specified
window until one of stopModal, abortModal, or stopModalWithCode: is invoked, typically by a button’s
action method. The stopModal method ends the modal status of the window or panel from within the event
loop. It doesn’t work if invoked from a method invoked by a timer or by a distributed object because those
mechanisms operate outside of the event loop. To terminate the modal loop in these situations, you can use
abortModal. The stopModal method is typically invoked when the user clicks the OK button (or equivalent),
abortModal when the user clicks the Cancel button (or presses the Escape key). These two methods are
equivalent to stopModalWithCode: with the appropriate argument.
The second mechanism for operating a modal window or panel, called a modal session, allowsthe application
to perform a long operation while it still sends events to the window or panel. Modal sessions are particularly
useful for panels that allow the user to cancel or modify an operation. To begin a modal session, invoke
beginModalSessionForWindow: on the application, which sets the window up for the session and returns
an identifier used for other session-controlling methods. At this point, the application can run in a loop that
performsthe operation, invoking runModalSession: on the application object on each passso that pending
events can be dispatched to the modal window. This method returns a code indicating whether the operation
should continue, stop, or abort, which is typically established by the methods described above for
runModalForWindow:. After the loop concludes, you can remove the window from the screen and invoke
endModalSession: on the application to restore the normal event loop.
Note: You can write a modal event loop for a view object so that the object has access to all events
pertaining to a particular task, such as tracking the mouse in the view. For an example, see
“Responding to User Events and Actions” in “Creating a Custom View”.
The normal behavior of a modal window or session is to exclude all other windows and panels from receiving
events. For windows and panels that serve as general auxiliary controls, such as menus and the Font panel,
this behavior is overly restrictive. The user must be able to use menu key equivalents (such as those for Cut
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
12
How Modal Windows Workand for Paste) and change the font of text in the modal window, and this requires that non-modal panels be
able to receive events. To support this behavior, an NSWindow subclass overridesthe worksWhenModal method
to return YES. This allows the window to receive mouse and keyboard events even when a modal window is
present. If a subclass needs to work when a modal window is present, it should generally be a subclass of
NSPanel, not of NSWindow.
Modal windows and modal sessions provide different levels of control to the application and the user. Modal
windows restrict all action to the window itself and any methods invoked from the window. Modal sessions
allow the application to continue an operation while accepting input only through the modal session window.
Beyond this, you can use distributed objects to perform background operations in a separate thread, while
allowing the user to perform other actions with any part of the application. The background thread can
communicate with the main thread, allowing the application to display the status of the operation in a
non-modal panel, perhaps including controls to stop or affect the operation as it occurs. Note that because
AppKit isn’t thread-safe, the background thread should communicate with a designated object in the main
thread that in turn interacts with the AppKit.
Before OS X version 10.6, if a modal window was open, application termination would be prevented if the user
attempted to terminate that window’s application. Beginning in OS X version 10.6, you can call
setPreventsApplicationTerminationWhenModal: with a value of NO, and the window will not prevent
application termination when modal. The current value of this property may be accessed by calling
preventsApplicationTerminationWhenModal. The default value is NO.
How Modal Windows Work
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
13A panel is a special kind of window, typically serving an auxiliary function in an application. The NSPanel
subclass of NSWindow adds a few special behaviors to windows in support of the role panels play:
● By default panels are not released when they’re closed, because they’re usually lightweight and often
reused.
● Onscreen panels, except for alert dialogs, are removed from the screen when the application isn’t active
and are restored when the application again becomes active. This reduces screen clutter.
Specifically, the NSWindow implementation of the hidesOnDeactivate method returns NO, but the
NSPanel implementation of the same method returns YES.
● Panels can become the key window, but they cannot become the main window.
●
If a panel is the key window and has a close button, it closes itself when the user presses the Escape key.
In addition to these automatic behaviors, the NSPanel class allows you to configure certain other behaviors
common to some kinds of panels:
● You can prevent a panel from becoming the key window unless the user clicks in a view that responds to
typing. This prevents the key window from shifting to the panel unnecessarily. The
setBecomesKeyOnlyIfNeeded: method controls this behavior.
● Palettes and similar panels can be made to float above standard windows and other panels. This prevents
them from being covered and keepsthem readily available to the user. The setFloatingPanel: method
controls this behavior.
● A panel can be made to receive mouse and keyboard events even when another window or panel is being
run modally or in a modal session. This permits actions in the panel to affect the modal window or panel.
The setWorksWhenModal: method controls this behavior. See “How Modal Windows Work” (page 12)
for more information on modal windows and panels.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
14
How Panels WorkA controller object (in this case, an instance of the NSWindowController class) manages a window; this object
is usually stored in a nib file. This management entails the following:
● Loading and displaying the window
● Closing the window when appropriate
● Customizing the window’s title
● Storing the window’s frame (size and location) in the defaults database
● Cascading the window in relation to other document windows of the application
A window controller can manage a window by itself or as a participant in AppKit’s document-based architecture,
which also includes the NSDocument and NSDocumentController classes. In this architecture, a window
controller is created and managed by a document (an instance of an NSDocument subclass) and, in turn, keeps
a reference to the document. For a discussion of this architecture, see Document-Based App Programming
Guide for Mac .
The relationship between a window controller and a nib file is important. Although a window controller can
manage a programmatically created window, it usually manages a window in a nib file. The nib file can contain
other top-level objects, including other windows, but the window controller’s responsibility is this primary
window. The window controller is usually the owner of the nib file, even when it is part of a document-based
application.
For simple documents—that is, documents with only one nib file containing a window—you need do little
directly with NSWindowController objects. AppKit creates one for you. However, if the default window
controller is not sufficient, you can create a custom subclass of NSWindowController.
For documents with multiple windows or panels, your document must create separate instances of
NSWindowController (or of custom subclasses of NSWindowController), one for each window or panel.
An example is a CAD application that has different windows for side, top, and front views of drawn objects.
What you do in your NSDocument subclass determines whether the default NSWindowController object or
separately created and configured NSWindowController objects are used.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
15
How Window Controllers WorkWindow Closing Behavior
When a window is closed and it is part of a document-based application, the document removes the window’s
window controller from itslist of window controllers. Thisresultsin the system deallocating the window controller
and the window, and possibly the NSDocument object itself. When a window controller is not part of a
document-based application, closing the window does not by default result in the deallocation of the window
or window controller. This is the desired behavior for a window controller that manages something like an
inspector; you shouldn’t have to load the nib file again and re-create the objectsthe next time the user requests
the inspector.
If you want the closing of a window to make both window and window controller go away when it isn’t part
of a document, yoursubclass of NSWindowController can observe the NSWindowWillCloseNotification
notification or, as the window delegate, implement the windowWillClose: method.
How Window Controllers Work
Window Closing Behavior
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
16This article describes how to open and close a window.
Opening a window—that is, making a window visible—is normally accomplished by placing the window into
the application's window list by invoking one of the methods makeKeyAndOrderFront:, orderFront:, etc.,
in NSWindow, and so on. Also, with certain bits set in Interface Builder, the window is shown when the nib file
is loaded in some cases.
Closing a window involves explicit use of either the close method, which simply removes the window from
the screen, or performClose:, which highlights the close button as though the user clicked it. Closing a
window involves at least removing it from the screen but may include disposing of it altogether. The
setReleasedWhenClosed: method specifies whether a window releases itself when it receives a close
message. A window’s delegate is also notified when it’s about to close, as described in “Using Window
Notifications and Delegate Methods” (page 41).
These methods hide a window without closing it. The method orderOut: removes a window from the screen.
You can also set a window to be removed from the screen automatically when its application isn’t active using
setHidesOnDeactivate:. The isVisible method returns whether a window is on or off the screen.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
17
Opening and Closing WindowsEach window is placed on the screen by a particular application, and each application typically owns a variety
of windows. Windows have numerous characteristics. They can be located onscreen or offscreen. Onscreen
windows are placed on the screen in levels managed by the window server.
Windows onscreen are ordered from front to back. Like sheets of paper loosely stacked together, windows in
front can overlap, or even completely cover, those behind them. Each window has a unique position in the
order. When two windows are placed side-by-side, one is still technically in front of the other.
If any window could be in front of any other window, then small but important windows—like menus and
tool palettes—might get lost behind larger ones. Windows that require user action, like attention panels and
pop-up lists, might disappear behind another window and go unnoticed. To prevent this, all the windows
onscreen are organized into levels.
When two windows belong to the same level, either one can be in front. When two windows belong to different
levels, however, the one in the higher level will always be above the other.
Onscreen windows can also carry a status: main or key . Offscreen windows are hidden or minimized on Dock,
and do not carry either status. Onscreen windows that are neither main nor key are inactive.
Window Layering
Each application and document window exists in its own layer, so documents from different applications can
be interleaved. Clicking a window to bring it to the front doesn’t disturb the layering order of any other window.
A window’s depth in the layers is determined by when the window was last accessed. When a user clicks an
inactive document or chooses it from the Window menu, only that document, and any open utility windows,
should be brought to the front. Users can bring all windows of an application forward by clicking its icon in
the Dock or by choosing Bring All to Front in the application’s Window menu. These actions should bring
forward all of the application’s open windows, maintaining their onscreen location, size, and layering order
within the application. For more information, see “UI Element Guidelines: Menus” in OS X Human Interface
Guidelines.
Utility windows are alwaysin the same layer: the top layer. They are visible only when their application is active.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
18
Window Layering and Types of WindowsKey and Main Windows
Windows have different looks based on how the user is interacting with them. The foremost document or
application window that is the focus of the user’s attention is referred to as the main window. Each application
also has only one main window at a given time. This main window often has key status, as well. The main
window is the principal focus of user actions for an application. Often, user actions in a modal key window
(typically a panel such as the Font window or an Info window) have a direct effect on the main window.
Main and key windows are both active windows. Active windows are visually distinct from inactive windows
in that their controls have color, while the controls in inactive windows do not have color. Inactive windows
are windows the user has open but that are not in the foreground. Main and key windows are always in the
foreground and their controls always have color. If the main and key window are different windows, they are
distinguished from one another by the look of their title bars. Note the visual distinctions between main, key,
and inactive windows in Figure 1.
Figure 1 Main, key, and inactive windows
Inactive window
Main window
Key window
A good example of the difference between key and main windows can be seen in most well-behaved Mac
apps. Selecting “Save As...” in a text document, for example, displays a panel with a field to type the document’s
name and a pull-down menu of locations to save it. The panel represents the key window. It will accept your
Window Layering and Types of Windows
Key and Main Windows
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
19keyboard input (the file name), but will directly affect the main window under it (by saving it to the location
you specified). Once you save the document, the save panel disappears, the main window becomes key again,
and will accept keyboard input once more.
The Key Window
The key window responds to user input, whether from the keyboard, mouse, or alternative input devices, for
an application and is the primary recipient of messages from menus and panels. Usually, a window is made
key when the user clicks it. Each application can have only one key window at a given time.
Users expect to see their actions on the keyboard and mouse take effect not only in a particular application,
but also in a particular window of that application. Each user action is associated with a window by the window
server and AppKit. Before acting, the user needs to know which window will be affected; there should be no
surprises.
Since the mouse controls the pointer, it's quite easy for the user to determine which window a mouse action
is associated with. It's whatever window the pointer is over. But the keyboard doesn’t have a pointer, so there’s
no natural way to determine where typed characters will appear.
To mark the key window for users, AppKit highlights its title bar. You can think of the highlighting as a kind
of pointer for the keyboard. It shifts from window to window as the key window changes. Key-window status
also moves from application to application as the active application changes. Only one window on the screen
is marked at a time, and it is in the active application. There’s just one key window on the Desktop. Even a
system that has two screens, but only one keyboard, has at most one key window.
Note: A window doesn’t have to become the key window to receive, and act on, keyboard shortcuts.
It does, however, have to be a window in the active application.
Since the key window belongs to the active application, its highlighted title bar has the secondary effect of
helping to show which application is currently active. The key window isthe most prominently marked window
in the active application, making it “key” in a second sense: it’s the main focus of the user’s attention on the
screen.
The Main Window
The main window is the standard window where the user is currently working. The main window is not always
the key window. There are times when a window other than the main window takes the focus of the input
device, while the main window still remains the focus of the user’s attention and of user actions carried out in
panels and menus. For example, when a person is using an inspector, a Find dialog, or the Fonts or Colors
windows, the document is the main window and the other window is the key window. The Find panel requires
Window Layering and Types of Windows
Key and Main Windows
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
20the user to supply information by typing it. Since the panel is the destination of the user’s keystrokes, it’s
marked as the key window. But the panel is just an instrument through which users can do work in another
window—the main window. In a document-based application, the main window isthe window for the current
document.
Whenever a standard window becomesthe key window, it also becomesthe main window. When key-window
status shifts from a standard window to a panel, main-window status remains with the standard window.
So that users can pick out the main window when it’s not the key window, the Application Kit highlights its
title bar and colorsthe window buttons. If the main window is also the key window, it has only the highlighting
of the key window. A menu command might affect either the key window or the main window, depending
on the command. For example, the Paste command can be used to enter text in a Find panel. But the Save
command saves the document displayed in the main window, and the Bold command turns the current
selection in the main window bold. For this reason, user actions in a panel or menu are associated with both
the key window and the main window:
● An action is first associated with the key window.
●
If the key window is a panel and it can’t handle the action, the action is next associated with the main
window.
Note that this order of precedence is reflected in the way windows are highlighted: The key window is always
marked, but the main window is marked only when it’s not the key window. The main window is always in the
same application as the key window, the active application.
Changing a Window’s Status
Windows that are already onscreen automatically change their status as the key or main window based on the
user’s actions with the mouse and on how clicked views handle those mouse events. You can also set the key
and main windows programmatically by sending the relevant windows a makeKeyWindow or makeMainWindow
message. Setting the key and main windows programmatically is particularly useful when creating a new
window. Because making a window key is often combined with ordering the window to the front of the screen,
the NSWindow class defines a convenience method, makeKeyAndOrderFront:, that performs both operations.
Not all windows are suitable as key or main windows. For example, a window that merely displays information
and contains no objectsthat need to respond to events or action messages can completely forgo ever becoming
the key window. Similarly, a window that acts as a floating palette of itemsthat are only dragged out by mouse
actions never needs to be the key window. Such a window can be defined as a subclass of NSWindow that
overridesthe methods canBecomeKeyWindow and canBecomeMainWindow to return NO instead of the default
of YES. Defining a window this way prevents it from ever becoming the key or main window. Although the
NSWindow class defines these methods, only subclasses of NSPanel typically refuse to accept key or main
window status.
Window Layering and Types of Windows
Key and Main Windows
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
21Windows can be placed on the screen in three dimensions. Besides horizontal and vertical placement, windows
are layered back-to-front within distinct levels. Each application and document window exists in its own layer,
so documents from different applications can be interleaved. Clicking a window to bring it to the front doesn’t
disturb the layering order of any other window. A window’s depth in the layers is determined by when the
window was last accessed. When a user clicks an inactive document or chooses it from the Window menu,
only that document and any open utility windows should be brought to the front.
Window Levels
Windows are ordered within several distinct levels. Window levels group windows ofsimilar type and purpose
so that the more “important” ones(such as alert panels) appear in front of those lesser importance. A window’s
level serves as a high-order bit to determine its position with regard to other windows. Windows can be
reordered with respect to each other within a given level; a given window, however, cannot be layered above
other windows in a higher level.
There are a number of predefined window levels, specified by constants defined by the NSWindow class. The
levels you typically use are: NSNormalWindowLevel, which specifies the default level;
NSFloatingWindowLevel, which specifiesthe level for floating palettes; and NSScreenSaverWindowLevel,
which specifies the level for a screen saver window. You might also use NSStatusWindowLevel for a status
window, or NSModalPanelWindowLevel for a modal panel. If you need to implement your own popup menus
you use NSPopUpMenuWindowLevel. The remaining two levels, NSTornOffMenuWindowLevel and
NSMainMenuWindowLevel, are reserved for system use.
Setting Ordering and Level Programmatically
You can use the orderWindow:relativeTo: method to order a window within its level in front of or in back
of another window. You more typically use convenience methods to specify ordering, such as
makeKeyAndOrderFront: (which also affectsstatus), orderFront:, and orderBack:, as well as orderOut:,
which removes a window from the screen. You use the isVisible method to determine whether a window
is on or off the screen. You can also set a window to be removed from the screen automatically when its
application isn’t active using setHidesOnDeactivate:.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
22
Window Layers and LevelsTypically you should have no need to programmatically set the level of a window, since Cocoa automatically
determines the appropriate level for a window based on its characteristics. A utility panel, for example, is
automatically assigned to NSFloatingWindowLevel. You can nevertheless set a window’s level using the
setLevel: method; for example, you can set the level of a standard window to NSFloatingWindowLevel
if you want a utility window that looks like a standard window (for example to act as an inspector). This has
two disadvantages, however: firstly, it may violate the human interface guidelines; secondly, if you assign a
window to a floating level, you must ensure that you also set it to hide on deactivation of your application or
reset its level when your application is hidden. Cocoa automatically takes care of the latter aspect for you if
you use default window configurations.
There is currently no level specified to allow you to place a window above a screen saver window. If you need
to do this (for example, to show an alert while a screen saver is running), you can set the window’s level to be
greater than that of the screen saver, as shown in the following example.
[aWindow setLevel:NSScreenSaverWindowLevel + 1];
Other than this specific case, you are discouraged from setting windows in custom levels since this may lead
to unexpected behavior.
Window Layers and Levels
Setting Ordering and Level Programmatically
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
23The are a number of different options that can be set regarding the window collection behavior of a window.
They include a window’s behavior when using Spaces, Exposé, and the “Cycle Through Windows” command.
These options can be set using the setCollectionBehavior: method of NSWindow, by passing in at most
one constant from each group, combined using bitwise or operators. The current options may be accessed via
the collectionBehavior method.
Spaces Collection Behavior
There are three options that can be set for a window’s Spaces collection behavior. The default is
NSWindowCollectionBehaviorDefault, which allows the window to be associated with one space at a
time. The second option is NSWindowCollectionBehaviorCanJoinAllSpaces. This option causes the
window to appear on all spaces, like the menu bar. The third option is
NSWindowCollectionBehaviorMoveToActiveSpace. This causesthe window to switch to the active space
when it is made active. Only one of these options may be used at a time.
If a window is currently associated with the active space, isOnActiveSpace returns YES. Otherwise, it returns
NO. Additionally, you can get an array of the window numbers of windows on one or all spaces using the
method windowNumbersWithOptions: and specified your desired options. The possible options are specified
by NSWindowNumberListOptions.
Exposé Collection Behavior
There are also three options that can be set for a window’s Exposé collection behavior. If a window has a
window level of NSNormalWindowLevel, the default behavior is NSWindowCollectionBehaviorManaged,
which causes the window to participate in both Spaces and Exposé.
NSWindowCollectionBehaviorTransient causes the window to float in Spaces and be hidden in Exposé.
This is the default behavior if the window level is not NSNormalWindowLevel. The final option is
NSWindowCollectionBehaviorStationary, which causes the window to be unaffected by Exposé; i.e. it
stays visible and does not move, like the desktop window. Only one of these options may be used at a time.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
24
Setting Window Collection BehaviorWindow Cycling Behavior
There are two options: NSWindowCollectionBehaviorParticipatesInCycle and
NSWindowCollectionBehaviorIgnoresCycle. These options cause the window to participate in the
window cycle for the “Cycle Through Windows” menu option or not participate in it, respectively.
Setting Window Collection Behavior
Window Cycling Behavior
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
25This article describes how to control a window’s size and position, including how to set a window’s minimum
and maximum size, how to constrain a window to the screen, how to cascade windowsso their title barsremain
visible, how to zoom a window as though the user pressed the zoom button, and how to center a window on
the screen.
Setting a Window’s Size and Location
The center method places a window in the most prominent location on the screen, one suitable for important
messages and alert dialogs.
You can resize or reposition a window using setFrame:display: or setFrame:display:animate:—the
former is equivalent to the latter with the animate flag NO. You might use these methodsin particular to expand
or contract a window to show or hide a subview (such as a control that may be exposed by clicking a disclosure
triangle). If the animate argument in setFrame:display:animate: is YES, the method performs a smooth
resize of the window, where the total time for the resize can be obtained by calling animationResizeTime:.
The user can resize windows by clicking and dragging on the bottom right corner of the window. While the
user is resizing the window, inLiveResize will return YES. Otherwise, it returns NO. The user can generally
reposition windows by dragging only the title bar. If you want usersto be able to drag your window by clicking
elsewhere, you should override mouseDownCanMoveWindow so that it returns YES in any views that you want
to be draggable window regions. The methods isMovable and setMovable: determine whether the user
can move the window by clicking in its title bar or background.
To keep the window’s top-left hand corner fixed when resizing, you must typically also reposition the origin,
as illustrated in the following example.
- (IBAction)showAdditionalControls:sender
{
NSRect frame = [myWindow frame];
if (frame.size.width <= MIN_WIDTH_WITH_ADDITIONS)
frame.size.width = MIN_WIDTH_WITH_ADDITIONS;
frame.size.height += ADDITIONS_HEIGHT;
frame.origin.y -= ADDITIONS_HEIGHT;
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
26
Sizing and Placing Windows[myWindow setFrame:frame display:YES animate:YES];
// implementation continues...
Note that the window’s delegate does not receive windowWillResize:toSize: messages when the window
is resized in this way. It is your responsibility to ensure that the window’s new size is acceptable.
The window’s delegate doesreceive windowDidResize: messages. You can implement windowDidResize:
to add or remove subviews at suitable junctures. There are no additional flags to denote that the window is
performing an animated resize operation (as distinct from a user-initiated resize). It is therefore up to you to
capture relevant state information so that you can update the window contents appropriately in
windowDidResize:.
Window Cascading
If you use the Cocoa document architecture, you can use the setShouldCascadeWindows: method of
NSWindowController to set whether the window, when it is displayed, should cascade in relation to other
document windows(that is, have a slightly offset location so that the title bars of previously displayed windows
are still visible). The default is true, so typically you have no additional work to perform.
If you are not using the document architecture, you can use the cascadeTopLeftFromPoint: method of
NSWindow to cascade windows yourself. The method returns a point shifted from the top-left corner of the
window that can be passed to a subsequent invocation of cascadeTopLeftFromPoint: to position the next
window so the title bars of both windows are fully visible.
Window Zooming
You use the zoom: method to toggle the size and location of a window between its standard state, as
determined by the application, and its user state: a new size and location the user may have set by moving or
resizing the window.
Constraining a Window’s Size and Location
You can use setContentMinSize: and setContentMaxSize: to limit the user’s ability to resize the
window—note that you can still set it to any size programmatically. Similarly, you can use
setContentAspectRatio: to keep a window’s width and height at the same proportions as the user resizes
it, and setContentResizeIncrements: to make the window resize in discrete amountslarger than a single
pixel. (Aspect ratio and resize increments are mutually exclusive attributes.) In general, you should use the
Sizing and Placing Windows
Constraining a Window’s Size and Location
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
27setContent... methodsinstead of those that affect the window’sframe (setAspectRatio:, setMaxSize:,
and so on). These are preferred because they avoid confusion for windows with toolbars, and also are typically
a better model since you control the content of the window but not the frame.
You can use the constrainFrameRect:toScreen: method to adjust a proposed frame rectangle so that
it lies on the screen in such a way that the user can move and resize a window. However, you should make
sure your window fits onscreen before display. Note that any NSWindow with a title bar automatically constrains
itself to the screen. The cascadeTopLeftFromPoint: method shifts the top left point by an amount that
allows one window to be placed relative to another so that both their title bars are visible.
Additionally, when a window is about to be resized, the window’s delegate will be sent a
windowWillResize:toSize: message. You can implement that method in your delegate to easily control
your window’s size.
Sizing and Placing Windows
Constraining a Window’s Size and Location
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
28A window can store its placement in the user defaults system, so that it appears in the same location the next
time the user starts the application. The saveFrameUsingName: method stores the frame rectangle, and
setFrameUsingName: setsit from the value in user defaults. You can also use the setFrameAutosaveName:
method to have a window save the frame rectangle any time it changes. However, for the correct frame to be
saved, you must ensure that the window controller for the window in question doesn’t cascade the windows
under its charge. You accomplish this task by sending setShouldCascadeWindows:NO to the controller, as
shown in Listing 1.
Listing 1 Saving a window’s frame automatically
NSWindow *window = // the window in question
[[window windowController] setShouldCascadeWindows:NO]; // Tell the controller
to not cascade its windows.
[window setFrameAutosaveName:[window representedFilename]]; // Specify the autosave
name for the window.
To expunge a frame rectangle from the defaults system, use the class method removeFrameUsingName:.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
29
Saving a Window’s Position into the User’s DefaultsWhen a user minimizes a window, it’s removed from the screen and replaced with a smaller counterpart in the
Dock.
The miniaturize: and deminiaturize: methods reduce and reconstitute a window, and
performMiniaturize: simulatesthe user clicking the window’s minimize button. You can also set the image
and title displayed in a freestanding mini-window by sending setMiniwindowImage: and
setMiniwindowTitle: messages to the NSWindow object.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
30
Minimizing WindowsMost Cocoa applications include the Window menu, which displays the titles of various of the application’s
windows. When you change a window’s title, this change is automatically reflected in the Window menu. This
menu automatically lists windowsthat have a title bar and are resizable and that can become the main window
(as described in “Window Layering and Types of Windows” (page 18)). Typically you can rely on the automatic
updating provided by Cocoa. In rare circumstances, however, you might want to modify the default behavior.
You can exclude a window that would otherwise be listed in the Window menu by sending it a
setExcludedFromWindowsMenu:YES message. Since they cannot become main, NSPanel objects are
excluded from the Windows menu. Instances of subclasses of NSPanel can be included in the menu by
returning NO from its isExcludedFromWindowsMenu method and YES from its canBecomeMainWindow
method. If you change a window’s configuration such that it should be added to or removed from the Window
menu, you can update the Window menu by sending the shared application instance
addWindowsItem:title:filename: or removeWindowsItem:.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
31
Using the Window MenuYou usually configure most aspects of a window’s appearance in Interface Builder. Sometimes, however, you
may need to create a window programmatically, or alter its appearance after it has been created.
Setting a Window’s Style
The peripheral elements that a window displays define its style. Though you can’t access and manipulate them
directly, you can determine at initialization whether a window has them by providing a style mask to the
initializer. There are four possible style elements,specifiable by combining their mask values using the C bitwise
OR operator:
Element Mask Value
A title bar NSTitledWindowMask
A close button NSClosableWindowMask
A minimize button NSMiniaturizableWindowMask
A resize bar, border, or box NSResizableWindowMask
You can also specify NSBorderlessWindowMask, in which case none of these style elements is used.
Typically, you set a window’s appearance once, when it is first created. Sometimes, however, you want to
enable or disable a button in the title bar to reflect changed context. To do this, you first retrieve the button
from the window using the standardWindowButton: of NSWindow method and then set its enabled state,
as in the following example.
NSButton *closeButton = [window standardWindowButton:NSWindowCloseButton];
[closeButton setEnabled:NO];
The constants required to access standard title bar widgets are defined in the API reference for NSWindow.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
32
Setting a Window’s AppearanceSetting a Window’s Color and Transparency
You can set a window’s background color and transparency using the methods setBackgroundColor: and
setAlphaValue:, respectively.
You can set a window’s background color to a non-opaque color. This does not affect the window’s title bar;
it only makes the background itself transparent if the window is not opaque, as illustrated in the following
example.
[myWindow setOpaque:NO]; // YES by default
NSColor *semiTransparentBlue =
[NSColor colorWithDeviceRed:0.0 green:0.0 blue:1.0 alpha:0.5];
[myWindow setBackgroundColor:semiTransparentBlue];
Views placed on a non-opaque window with a transparent background color retain their own opacity. If you
want to make the entire window (including the title bar and views placed on the window) transparent, you
should use setAlphaValue:.
Setting a Window’s Color Space
You can set a window’s colorspace using setColorSpace: and can retrieve the window’s current colorspace
using colorSpace. NSColorSpace objects for use with setColorSpace: may be obtained using the class
methods documented in NSColorSpace Class Reference .
Setting a Window’s Content Border Thickness
Beginning in OS X version 10.5, windows automatically have a textured gradient applied to their backgrounds.
The area on which the gradient is drawn is determined automatically. At times, however, this may not work
correctly. If your window does not look correct with automatic gradient calculation, disable it by calling
setAutorecalculatesContentBorderThickness:forEdge: with a value of NO and the edge to disable
automatic calculation for. The value of this property may be accessed using the method
autorecalculatesContentBorderThicknessForEdge:.
You can also set and access the content border thickness manually using
setContentBorderThickness:forEdge: and contentBorderThicknessForEdge:, respectively.
Setting a Window’s Appearance
Setting a Window’s Color and Transparency
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
33A titled window can display an arbitrary title or one derived from a filename. The setTitle: method puts an
arbitrary string on the title bar. The setTitleWithRepresentedFilename: method formats a filename in
the title bar in a readable format and associates the window with that file. You can set the associated file
without changing the title using setRepresentedFilename:. You can use the association between the
window and the file in any way you see fit. One convenience offered by the NSWindow class is marking the
file as having been changed, so that the user is prompted to save it on closing the window. The method for
marking the document as having been changed is setDocumentEdited:. When the window closes, its
delegate can check if the files has been changed using isDocumentEdited to see whether the document
needs to be saved.
Additionally, starting in OS X version 10.5, you can set a window’s represented document by URL using the
setRepresentedURL: method. You can get the URL of the document currently represented by a window
using the representedURL method. The window will automatically use the known icon for the file type of
the specified file, if one exists. To customize the document icon, you can use the following code segment:
[[NSWindow standardWindowButton:NSWindowDocumentIconButton] setImage:customImage].
By default, a Command-click or Control-click on the rectangle containing a window’s document icon button
and title will show a path popup. To customize this behavior, you can implement
window:shouldPopUpDocumentPathMenu: in your window’s delegate. You can return NO from this method
to stop the window from showing the path popup.
You can also customize the document icon’s default drag behavior by implementing the
window:shouldDragDocumentWithEvent:from:withPasteboard: in the window’s delegate. You can
return NO to prohibit dragging the document icon.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
34
Setting a Window’s Title and Represented FileNearly every window has a corresponding display window device in the window server. The window device
holdsthe window’s drawn image, and hastwo attributes determined by the window server and many attributes
that the window controls. The window server assigns the window device a unique identifier (within an
application). This is the window number, and it can be accessed using the windowNumber method. Each
window also has a graphics state that most of its views share for drawing (views can create their own as well).
The gState method returns its identifier. The attributes under direct window control are the following:
● Backing store type, described in “Specifying How To Store the Window’s Image” (page 35)
● Backing location, described in “Specifying Where To Store the Window’s Image” (page 36)
● Window device creation, described in “Specifying When the Window’s Image Is Created” (page 36)
● One shot, described in “Specifying Whether the Window’s Image Persists When Offscreen” (page 37)
● Depth limit, described in “Specifying the Depth Limit for the Window’s Image” (page 37)
● Dynamic depth limit, described in “Specifying Whether the Depth Limit Changes to the Screen’s
Capacity” (page 37)
● Content sharing, described in “Specifying Whether Window Content Can Be Read or Written by Another
Process” (page 37).
Specifying How To Store the Window’s Image
A window device’s backing store type determines how the window’simage isstored. It’sset when the window
is initialized and can be one of three types.
A buffered window device renders all drawing into a display buffer and then flushes it to the screen. Always
drawing to the buffer produces very smooth display, but can require significant amounts of memory. Buffered
windows are best for displaying material that must be redrawn often, such as text. You must also use buffered
windows if you want your windows to support transparency.
A retained window device also uses a buffer, but draws directly to the screen where possible and to the buffer
for any portions that are obscured.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
35
Setting Attributes for the Window’s ImageA nonretained window device has no buffer at all, and must redraw portions as they’re exposed. Further, this
redrawing is suspended when the window’s display mechanism is preempted. For example, if the user drags
a window across a nonretained window, the nonretained window is “erased” and isn’t redrawn until the user
releases the mouse.
Both retained and nonretained windows are also subject to a flashing effect as individual drawing operations
are performed, but their results do get to the screen more quickly than those of buffered windows.
You can change the backing store type between buffered and retained after initialization using the
setBackingType: method.
Specifying Where To Store the Window’s Image
The window server chooses whether to place the backing store for a buffered window in main memory or
video memory. It will choose the location that providesthe best overall performance. You can query the window
server to determine where your window’s backing store is located using the preferredBackingLocation
method.
You may choose to set a preferred location for a Window’s backing store using the
setPreferredBackingLocation: method. While the window server is not required to respect this preferred
backing location, it will attempt to do so. You should not change the preferred backing location without testing
how it affects the performance of your application.
Specifying When the Window’s Image Is Created
The defer argument to the initializer specifies whether the window creates its window device immediately
or only when it’s moved on screen. Deferring creation of the window device can offer some performance gain
for windows that aren’t displayed immediately because it reduces the amount of work that needs to be
performed up front. Deferring creation of the window device is particularly useful when creation of the window
itself can’t be deferred or when an window is needed for purposes other than displaying content. Submenus
with key equivalents, for example, must exist for the key equivalents to work, but may never actually be
displayed.
Setting Attributes for the Window’s Image
Specifying Where To Store the Window’s Image
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
36Specifying Whether the Window’s Image Persists When Offscreen
Memory can also be saved by destroying the window device when the window is removed from the screen.
The setOneShot: method controls this behavior. One-shot window devices exist only when their windows
are onscreen.
Specifying the Depth Limit for the Window’s Image
Like the display hardware, a window device’s buffer has a depth, or a limit to the memory allotted each pixel.
Buffered and retained windows start out with the same depth as the main display or 16 bits, whichever is
deeper. These settings stay in effect unless changed using the setDepthLimit: method, which takes as an
argument a window depth limit created using the NSBestDepth function.
SpecifyingWhether the Depth Limit Changesto the Screen’s Capacity
Keeping a window’s depth at its richest preserves the displayed image, but may incur unnecessary memory
overhead when the window buffer depth is deeper than the screen depth. You can use the
setDynamicDepthLimit: method to tell a window to match the depth of the screen it’s on. When it’s moved
to a new screen, a window with a dynamic depth limit adjusts its buffer to the new depth before redrawing.
Making a window’s depth limit dynamic overrides the limit set using setDepthLimit:, and removing the
dynamic limit reverts the window to the default limit.
Specifying Whether Window Content Can Be Read or Written by
Another Process
The contents of your window can be made available to other processes. By default, the contents of your window
can be read but not written to by other processes. This allows system services to work with your window’s
contents and also allows other applications to capture a snapshot of your windows contents.
You can override the default behavior using the setSharingType: method. Changing the sharing type to
NSWindowSharingNone prevents other systems from capturing your window’s image data. If you do this,
however, your window will not be able to participate in a number of system services; therefore, this setting
should be used with caution. If you set your window’s sharing type to NSWindowSharingReadWrite, other
processes can both read and modify the window’s content.
Setting Attributes for the Window’s Image
Specifying Whether the Window’s Image Persists When Offscreen
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
37As described in NSResponder Class Reference , most events coming into an application make their way to a
window in a sendEvent: message. A key event is directed at the key window, while a mouse event is directed
at whatever window lies under the pointer. If an event affects the window directly—resizing or moving it, for
example—it performs the appropriate operation itself and sends messages to its delegate informing it of its
intentions, thus allowing your application to intercede. The window sends other events up its responder chain
from the appropriate starting point: the first responder for a key event, the view under the pointer for a mouse
event. These events are then typically handled by some view object in the window. See Cocoa Event Handling
Guide for more information on how to intercept and handle events.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
38
Handling Events in WindowsA window’s first responder is often a view object selected by the user clicking it. For text fields and other view
objects (mainly subclasses of NSControl), the user can select the first responder with the keyboard using the
Tab and Shift keys. The NSView class defines the methods for setting up and examining the loop of objects
that the user can select in this manner. A view that’s the first responder is called the key view, and the views
that can become the key view in a window are linked together in the window’s key view loop. You normally
set up the key view loop using Interface Builder, establishing connections between the nextKeyView outlets
of views in the window and setting the window’s initialFirstResponder outlet to the view that you want
selected when the window is first placed onscreen. If you do not set this outlet, the window sets a key loop
(not necessarily the same as the one you would have specified!) and picks a default initial first responder for
you.
In addition to the key view loop, a window can have a default button cell, which uses the Return (or Enter)
key as its key equivalent. The setDefaultButtonCell: method establishes this button cell; you can also
set it in Interface Builder by setting a button cell’s key equivalent to '\r'. The default button cell draws itself
as a focal element for keyboard interface control unless another button cell is focused on. In this case, it
temporarily draws itself as normal and disables its key equivalent. Another default key established by the
NSWindow class is the Escape key, which immediately aborts a modal loop (described in “How Modal Windows
Work” (page 12)).
See NSResponder Class Reference for more information on keyboard interface control.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
39
Using Keyboard Interface Control in WindowsEach window has a text object that is shared for light editing tasks. This object, the window’s field editor, is
inserted in the view hierarchy when an object needsto editsome text and removed when the object isfinished.
The field editor is used by NSTextField objects and other controls, for example, to edit the text that they
display. The fieldEditor:forObject: method returns a window’s field editor, after asking the delegate
for a substitute using windowWillReturnFieldEditor:toObject:. You can override the
fieldEditor:forObject: method of NSWindow in subclasses or provide a delegate to substitute a class
of text object different from the NSTextView default, thereby customizing text editing in your application.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
40
Using the Window’s Field EditorThe NSWindow class offers observers a rich set of notifications, which it broadcasts on such occurrences as
gaining or losing key or main window status, minimizing, moving or resizing, becoming exposed, and closing.
Each notification is matched to a delegate method, so a window’s delegate is automatically registered for all
notifications that it has methods for. The NSWindow class also offers its delegate a few other methods, such
as windowShouldClose:, which requests approval to close, windowWillResize:toSize:, which allows
the delegate to constrain the window’ssize, windowWillUseStandardFrame:defaultFrame:, which allows
the delegate to set the window frame for zooming, and windowWillReturnFieldEditor:toObject:,
which gives the delegate a chance to modify the field editor or substitute a different editor. See the individual
notification and delegate method descriptions for more information.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
41
Using Window Notifications and Delegate MethodsThe NSWindow class defines some methods for image dragging, in case the user wants to drag an object into
or out of a window. Although most dragging operations are initiated by and occur between view objects, the
NSWindow class also defines an image-dragging method,
dragImage:at:offset:event:pasteboard:source:slideBack:. A window can also serve as the
destination for dragging operations, registering the types it accepts with registerForDraggedTypes: and
unregisterDraggedTypes.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
42
Dragging Images to and from WindowsYou can change the cursor image when the cursor is within a specified area of a view in a window. To do this,
use the NSTrackingArea class, along with the cursorUpdate: method of the NSResponder class. For
specifics, read “Using Tracking-Area Objects” in Cocoa Event Handling Guide .
For details on the NSTrackingArea class itself, refer to NSTrackingArea Class Reference .
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
43
Updating the Cursor Image in a WindowTo support transitory drawing by views, the NSWindow class defines methods that temporarily cache a portion
of its raster image so that it can be restored later. This feature is useful for situations where highly dynamic
drawing must be done over the otherwise static image of the window. For example, in a drawing program
where the user drags lines and other shapes directly onto a canvas, it’s more efficient to restore the window’s
cached image and draw anew over that than to have all the views send display instructions to the window
server. For more information,see the method descriptionsfor cacheImageInRect:, restoreCachedImage,
and discardCachedImage.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
44
Caching Window ImagesThis table describes the changes to Window Programming Guide .
Date Notes
Revised the article “Updating the Cursor Image in a Window” (page 43),
previously titled “Setting Pointer Rectangles for Windows.”
2009-11-27
2009-05-15 Updated for OS X v10.6.
Added information on the use of backing locations to improve
performance.
2009-02-04
2008-10-15 Provided links to delegate methods.
Clarified the behavior of the setFrameAutosaveName: method in
conjunction with a window's window controller.
2006-10-03
Added window-controller requirement for the NSWindow
setFrameAutosaveName: method to “Saving a Window’s Position into
the User’s Defaults” (page 29).
Made correction to "Using the Windows Menu" article. Changed title from
"Windows and Panels."
2005-09-08
Updated “Setting a Window’s Appearance” (page 32) to cover enabling
and disabling buttons in the title bar, and to discuss setting a window’s
background color and transparency.
2004-08-31
“Setting a Window’s Level” renamed “Window Layers and Levels” (page
22) and augmented.
“Changing the Key and Main Windows” renamed to “Window Layering
and Types of Windows” (page 18) and augmented.
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
45
Document Revision HistoryDate Notes
Augmented “Sizing and Placing Windows” (page 26) to discuss animated
resizing, window cascading, and constraining window size and position.
Minor changes to “Using the Window Menu” (page 31).
Clarified the concepts of key and main windowsin “Window Layering and
Types of Windows” (page 18)“.
2003-06-05
2002-11-12 Revision history was added to existing topic.
Document Revision History
2009-11-27 | © 2002, 2009 Apple Inc. All Rights Reserved.
46Apple Inc.
© 2002, 2009 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Cocoa, Exposé, Mac,
Numbers, OS X, and Spaces are trademarks of
Apple Inc., registered in the U.S. and other
countries.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
AV Foundation
Programming GuideContents
About the AV Foundation Framework 4
At a Glance 5
Representing and Using Media with AV Foundation 5
Concurrent Programming with AV Foundation 7
Prerequisites 8
Using Assets 9
Creating an Asset Object 9
Options for Initializing an Asset 9
Accessing the User’s Assets 10
Preparing an Asset for Use 11
Getting Still Images From a Video 12
Generating a Single Image 13
Generating a Sequence of Images 14
Trimming and Transcoding a Movie 15
Reading and Writing Assets 17
Playback 18
Playing Assets 18
Handling Different Types of Asset 20
Playing an Item 21
Changing the Playback Rate 21
Seeking—Repositioning the Playhead 22
Playing Multiple Items 23
Monitoring Playback 23
Responding to a Change in Status 24
Tracking Readiness for Visual Display 25
Tracking Time 25
Reaching the End of an Item 26
Putting it all Together: Playing a Video File Using AVPlayerLayer 26
The Player View 27
A Simple View Controller 27
Creating the Asset 28
Responding to the Player Item’s Status Change 30
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
2Playing the Item 31
Media Capture 32
Use a Capture Session to Coordinate Data Flow 33
Configuring a Session 34
Monitoring Capture Session State 35
An AVCaptureDevice Object Represents an Input Device 35
Device Characteristics 36
Device Capture Settings 36
Configuring a Device 40
Switching Between Devices 41
Use Capture Inputs to Add a Capture Device to a Session 41
Use Capture Outputs to Get Output from a Session 42
Saving to a Movie File 43
Processing Frames of Video 46
Capturing Still Images 47
Showing the User What’s Being Recorded 49
Video Preview 49
Showing Audio Levels 50
Putting it all Together: Capturing Video Frames as UIImage Objects 50
Create and Configure a Capture Session 51
Create and Configure the Device and Device Input 51
Create and Configure the Data Output 52
Implement the Sample Buffer Delegate Method 52
Starting and Stopping Recording 53
Time and Media Representations 54
Representation of Assets 54
Representations of Time 55
CMTime Represents a Length of Time 55
CMTimeRange Represents a Time Range 57
Representations of Media 58
Converting a CMSampleBuffer to a UIImage 59
Document Revision History 62
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
3
ContentsAV Foundation is one of several frameworks that you can use to play and create time-based audiovisual media.
It provides an Objective-C interface you use to work on a detailed level with time-based audiovisual data. For
example, you can use it to examine, create, edit, or reencode media files. You can also get input streams from
devices and manipulate video during realtime capture and playback.
Core Audio
UIKit
Media Player
AV Foundation
iOS 3 Audio
classes
Core Media Core Animation
You should typically use the highest-level abstraction available that allows you to perform the tasks you want.
For example, in iOS:
●
If you simply want to play movies, you can use the Media Player Framework (MPMoviePlayerController
or MPMoviePlayerViewController), or for web-based media you could use a UIWebView object.
● To record video when you need only minimal control over format, use the UIKit framework
(UIImagePickerController).
Note, however, thatsome of the primitive data structuresthat you use in AV Foundation—including time-related
data structures and opaque objectsto carry and describemedia data—are declared in the Core Media framework.
AV Foundation is available in iOS 4 and later, and OS X 10.7 and later. This document describes AV Foundation
as introduced in iOS 4.0. To learn about changes and additions to the framework in subsequent versions, you
should also read the appropriate release notes:
● AV Foundation Release Notes describe changes made for iOS 5.
● AV Foundation Release Notes (iOS 4.3) describe changes made for iOS 4.3 and included in OS X 10.7.
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
4
About the AV Foundation FrameworkRelevant Chapters: “Time and Media Representations” (page 54)
At a Glance
There are two facets to the AV Foundation framework—API related just to audio, which was available prior to
iOS 4; and API introduced in iOS 4 and later. The older audio-related classes provide easy ways to deal with
audio. They are described in Multimedia Programming Guide , not in this document.
● To play sound files, you can use AVAudioPlayer.
● To record audio, you can use AVAudioRecorder.
You can also configure the audio behavior of your application using AVAudioSession; this is described in
Audio Session Programming Guide .
Representing and Using Media with AV Foundation
The primary class that the AV Foundation framework uses to represent media is AVAsset. The design of the
framework is largely guided by this representation. Understanding its structure will help you to understand
how the framework works. An AVAsset instance is an aggregated representation of a collection of one or
more pieces of media data (audio and video tracks). It provides information about the collection as a whole,
such as its title, duration, natural presentation size, and so on. AVAsset is not tied to particular data format.
AVAsset is the superclass of other classes used to create asset instances from media at a URL (see “Using
Assets” (page 9)) and to create new compositions (see “Editing” (page 7)).
Each of the individual pieces of media data in the asset is of a uniform type and called a track. In a typical
simple case, one track represents the audio component, and another represents the video component; in a
complex composition, however, there may be multiple overlapping tracks of audio and video. Assets may also
have metadata.
A vital concept in AV Foundation is that initializing an asset or a track does not necessarily mean that it is ready
for use. It may require some time to calculate even the duration of an item (an MP3 file, for example, may not
contain summary information). Rather than blocking the current thread while a value is being calculated, you
ask for values and get an answer back asynchronously through a callback that you define using a block.
About the AV Foundation Framework
At a Glance
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
5Relevant Chapters: “Using Assets” (page 9)
“Time and Media Representations” (page 54)
Playback
AVFoundation allows you to manage the playback of asset in sophisticated ways. To support this, it separates
the presentation state of an asset from the asset itself. This allows you to, for example, play two different
segments of the same asset at the same time rendered at different resolutions. The presentation state for an
asset is managed by a player item object; the presentation state for each tracks within an asset is managed
by a player item track objects. Using the player item and player item tracks you can, for example, set the size
at which the visual portion of the item is presented by the player, set the audio mix parameters and video
composition settings to be applied during playback, or disable components of the asset during playback.
You play player items using a player object, and direct the output of a player to Core Animation layer. On
iOS 4.1 and later, you can use a player queue to schedule playback of a collection of player items in sequence.
Relevant Chapters: “Playback” (page 18)
Reading, Writing, and Reencoding Assets
AV Foundation allows you to create new representations of an asset in several ways. You can simply reencode
an existing asset, or—on iOS 4.1 and later—you can perform operations on the contents of an asset and save
the result as a new asset.
You use an export session to reencode an existing asset into a format defined by one of a small number of
commonly-used presets. If you need more control over the transformation, on iOS 4.1 and later you can use
an asset reader and asset writer object in tandem to convert an asset from one representation to another.
Using these objects you can, for example, choose which of the tracks you want to be represented in the output
file, specify your own output format, or modify the asset during the conversion process.
To produce a visual representation of the waveform, you use an asset reader to read the audio track of an
asset.
About the AV Foundation Framework
At a Glance
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
6Relevant Chapters: “Using Assets” (page 9)
Thumbnails
To create thumbnail images of video presentations, you initialize an instance of AVAssetImageGenerator
using the asset from which you want to generate thumbnails. AVAssetImageGenerator uses the default
enabled video track(s) to generate images.
Relevant Chapters: “Using Assets” (page 9)
Editing
AV Foundation uses compositions to create new assets from existing pieces of media (typically, one or more
video and audio tracks). You use a mutable composition to add and remove tracks, and adjust their temporal
orderings. You can also set the relative volumes and ramping of audio tracks; and set the opacity, and opacity
ramps, of video tracks. A composition is an assemblage of pieces of media held in memory. When you export
a composition using an export session, it's collapsed to a file.
On iOS 4.1 and later, you can also create an asset from media such as sample buffers or still images using an
asset writer.
Media Capture and Access to Camera
Recording input from cameras and microphonesis managed by a capture session. A capture session coordinates
the flow of data from input devices to outputs such as a movie file. You can configure multiple inputs and
outputs for a single session, even when the session is running. You send messages to the session to start and
stop data flow.
In addition, you can use an instance of preview layer to show the user what a camera is recording.
Relevant Chapters: “Media Capture” (page 32)
Concurrent Programming with AV Foundation
Callouts from AV Foundation—invocations of blocks, key-value observers, or notification handlers—are not
guaranteed to be made on any particular thread or queue. Instead, AV Foundation invokes these handlers on
threads or queues on which it performs its internal tasks. You are responsible for testing whether the thread
or queue on which a handler isinvoked is appropriate for the tasks you want to perform. If it’s not (for example,
if you want to update the user interface and the callout is not on the main thread), you must redirect the
execution of your tasks to a safe thread or queue that you recognize, or that you create for the purpose.
About the AV Foundation Framework
At a Glance
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
7If you’re writing amultithreaded application, you can use the NSThreadmethod isMainThread or [[NSThread
currentThread] isEqual:<#A stored thread reference#>] to test whether the invocation thread
is a thread you expect to perform your work on. You can redirect messages to appropriate threads using
methods such as performSelectorOnMainThread:withObject:waitUntilDone: and
performSelector:onThread:withObject:waitUntilDone:modes:. You could also use
dispatch_async(3) OS X Developer Tools Manual Page to “bounce”to your blocks on an appropriate
queue, either the main queue for UI tasks or a queue you have up for concurrent operations. For more about
concurrent operations, see Concurrency Programming Guide ; for more about blocks, see Blocks Programming
Topics.
Prerequisites
AV Foundation is an advanced Cocoa framework. To use it effectively, you must have:
● A solid understanding of fundamental Cocoa development tools and techniques
● A basic grasp of blocks
● A basic understanding of key-value coding and key-value observing
● For playback, a basic understanding of Core Animation (see Core Animation Programming Guide )
About the AV Foundation Framework
Prerequisites
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
8Asset can come from a file or from media in the user’s iPod Library or Photo library. Simply creating an asset
object, though, does not necessarily mean that all the information that you might want to retrieve for that
item is immediately available. Once you have a movie asset, you can extract still images from it, transcode it
to another format, or trim the contents.
Creating an Asset Object
To create an asset to represent any resource that you can identify using a URL, you use AVURLAsset. The
simplest case is creating an asset from a file:
NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
Options for Initializing an Asset
AVURLAsset’s initialization methods take as their second argument an options dictionary. The only key used
in the dictionary is AVURLAssetPreferPreciseDurationAndTimingKey. The corresponding value is a
boolean (contained in an NSValue object) that indicates whether the asset should be prepared to indicate a
precise duration and provide precise random access by time.
Getting the exact duration of an asset may require significant processing overhead. Using an approximate
duration is typically a cheaper operation and sufficient for playback. Thus:
●
If you only intend to play the asset, either pass nil instead of a dictionary, or pass a dictionary that contains
the AVURLAssetPreferPreciseDurationAndTimingKey key and a corresponding value of NO
(contained in an NSValue object).
●
If you want to add the asset to a composition (AVMutableComposition), you typically need precise
randomaccess. Pass a dictionary that containsthe AVURLAssetPreferPreciseDurationAndTimingKey
key and a corresponding value of YES (contained in an NSValue object—recall that NSNumber inherits
from NSValue):
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
9
Using AssetsNSURL *url = <#A URL that identifies an audiovisual asset such as a movie
file#>;
NSDictionary *options = @{ AVURLAssetPreferPreciseDurationAndTimingKey :
@YES };
AVURLAsset *anAssetToUseInAComposition = [[AVURLAsset alloc]
initWithURL:url options:options];
Accessing the User’s Assets
To access the assets managed the iPod Library or by the Photos application, you need to get a URL of the asset
you want.
● To access the iPod Library, you create an MPMediaQuery instance to find the item you want, then get its
URL using MPMediaItemPropertyAssetURL.
For more about the Media Library, see Multimedia Programming Guide .
● To access the assets managed by the Photos application, you use ALAssetsLibrary.
The following example shows how you can get an asset to represent the first video in the Saved Photos Album.
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
// Enumerate just the photos and videos group by using ALAssetsGroupSavedPhotos.
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup
*group, BOOL *stop) {
// Within the group enumeration block, filter to enumerate just videos.
[group setAssetsFilter:[ALAssetsFilter allVideos]];
// For this example, we're only interested in the first item.
[group enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:0]
options:0
usingBlock:^(ALAsset *alAsset, NSUInteger index, BOOL
*innerStop) {
// The end of the enumeration is signaled by asset ==
nil.
if (alAsset) {
Using Assets
Creating an Asset Object
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
10ALAssetRepresentation *representation = [alAsset
defaultRepresentation];
NSURL *url = [representation url];
AVAsset *avAsset = [AVURLAsset URLAssetWithURL:url
options:nil];
// Do something interesting with the AV asset.
}
}];
}
failureBlock: ^(NSError *error) {
// Typically you should handle an error more gracefully than
this.
NSLog(@"No groups");
}];
Preparing an Asset for Use
Initializing an asset (or track) does not necessarily mean that all the information that you might want to retrieve
for that item is immediately available. It may require some time to calculate even the duration of an item (an
MP3 file, for example, may not contain summary information). Rather than blocking the current thread while
a value is being calculated, you should use the AVAsynchronousKeyValueLoading protocol to ask for values
and get an answer back later through a completion handler you define using a block. (AVAsset and
AVAssetTrack conform to the AVAsynchronousKeyValueLoading protocol.)
You test whether a value is loaded for a property using statusOfValueForKey:error:. When an asset is
first loaded, the value of most or all of its properties is AVKeyValueStatusUnknown. To load a value for one
or more properties, you invoke loadValuesAsynchronouslyForKeys:completionHandler:. In the
completion handler, you take whatever action is appropriate depending on the property’s status. You should
always be prepared for loading to not complete successfully, either because it failed for some reason such as
a network-based URL being inaccessible, or because the load was canceled. .
NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
NSArray *keys = @[@"duration"];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() {
Using Assets
Preparing an Asset for Use
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
11NSError *error = nil;
AVKeyValueStatus tracksStatus = [asset statusOfValueForKey:@"duration"
error:&error];
switch (tracksStatus) {
case AVKeyValueStatusLoaded:
[self updateUserInterfaceForDuration];
break;
case AVKeyValueStatusFailed:
[self reportError:error forAsset:asset];
break;
case AVKeyValueStatusCancelled:
// Do whatever is appropriate for cancelation.
break;
}
}];
If you want to prepare an asset for playback, you should load its tracks property. For more about playing
assets, see “Playback” (page 18).
Getting Still Images From a Video
To get still images such as thumbnails from an asset for playback, you use an AVAssetImageGenerator
object. You initialize an image generator with your asset. Initialization may succeed, though, even if the asset
possesses no visual tracks at the time of initialization, so if necessary you should test whether the asset has
any tracks with the visual characteristic using tracksWithMediaCharacteristic:.
AVAsset anAsset = <#Get an asset#>;
if ([anAsset tracksWithMediaCharacteristic:AVMediaTypeVideo]) {
AVAssetImageGenerator *imageGenerator =
[AVAssetImageGenerator assetImageGeneratorWithAsset:anAsset];
// Implementation continues...
Using Assets
Getting Still Images From a Video
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
12You can configure several aspects of the image generator, for example, you can specify the maximum dimensions
for the images it generates and the aperture mode using maximumSize and apertureMode respectively.You
can then generate a single image at a given time, or a series of images. You must ensure that you keep a strong
reference to the image generator until it has generated all the images.
Generating a Single Image
You use copyCGImageAtTime:actualTime:error: to generate a single image at a specific time. AV
Foundation may not be able to produce an image at exactly the time you request, so you can pass as the
second argument a pointer to a CMTime that upon return contains the time at which the image was actually
generated.
AVAsset *myAsset = <#An asset#>];
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc]
initWithAsset:myAsset];
Float64 durationSeconds = CMTimeGetSeconds([myAsset duration]);
CMTime midpoint = CMTimeMakeWithSeconds(durationSeconds/2.0, 600);
NSError *error;
CMTime actualTime;
CGImageRef halfWayImage = [imageGenerator copyCGImageAtTime:midpoint
actualTime:&actualTime error:&error];
if (halfWayImage != NULL) {
NSString *actualTimeString = (NSString *)CMTimeCopyDescription(NULL, actualTime);
NSString *requestedTimeString = (NSString *)CMTimeCopyDescription(NULL,
midpoint);
NSLog(@"Got halfWayImage: Asked for %@, got %@", requestedTimeString,
actualTimeString);
// Do something interesting with the image.
CGImageRelease(halfWayImage);
}
Using Assets
Getting Still Images From a Video
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
13Generating a Sequence of Images
To generate a series of images, you send the image generator a
generateCGImagesAsynchronouslyForTimes:completionHandler:message. The first argument is an
array of NSValue objects, each containing a CMTime, specifying the asset times for which you want images
to be generated. The second argument is a block that serves as a callback invoked for each image that is
generated. The block arguments provide a result constant that tells you whether the image was created
successfully or if the operation was canceled, and, as appropriate:
● The image.
● The time for which you requested the image and the actual time for which the image was generated.
● An error object that describes the reason generation failed.
In your implementation of the block, you should check the result constant to determine whether the image
was created. In addition, you must ensure that you keep a strong reference to the image generator until it has
finished creating the images.
AVAsset *myAsset = <#An asset#>];
// Assume: @property (strong) AVAssetImageGenerator *imageGenerator;
self.imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:myAsset];
Float64 durationSeconds = CMTimeGetSeconds([myAsset duration]);
CMTime firstThird = CMTimeMakeWithSeconds(durationSeconds/3.0, 600);
CMTime secondThird = CMTimeMakeWithSeconds(durationSeconds*2.0/3.0, 600);
CMTime end = CMTimeMakeWithSeconds(durationSeconds, 600);
NSArray *times = @[NSValue valueWithCMTime:kCMTimeZero],
[NSValue valueWithCMTime:firstThird], [NSValue
valueWithCMTime:secondThird],
[NSValue valueWithCMTime:end]];
[imageGenerator generateCGImagesAsynchronouslyForTimes:times
completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime
actualTime,
AVAssetImageGeneratorResult result, NSError
*error) {
NSString *requestedTimeString = (NSString *)
CFBridgingRelease(CMTimeCopyDescription(NULL, requestedTime));
Using Assets
Getting Still Images From a Video
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
14NSString *actualTimeString = (NSString *)
CFBridgingRelease(CMTimeCopyDescription(NULL, actualTime));
NSLog(@"Requested: %@; actual %@", requestedTimeString,
actualTimeString);
if (result == AVAssetImageGeneratorSucceeded) {
// Do something interesting with the image.
}
if (result == AVAssetImageGeneratorFailed) {
NSLog(@"Failed with error: %@", [error localizedDescription]);
}
if (result == AVAssetImageGeneratorCancelled) {
NSLog(@"Canceled");
}
}];
You can cancel the generation of the image sequence by sending the image generator a
cancelAllCGImageGeneration message.
Trimming and Transcoding a Movie
You can transcode a movie from one format to another, and trim a movie, using an AVAssetExportSession
object. An export session is a controller object that manages asynchronous export of an asset. You initialize
the session using the asset you want to export and the name of a export preset that indicates the export
options you want to apply (see allExportPresets). You then configure the export session to specify the
output URL and file type, and optionally other settings such as the metadata and whether the output should
be optimized for network use.
Asset
Export preset
AVAssetExportSession
URL
You can check whether you can export a given asset using a given preset using
exportPresetsCompatibleWithAsset: as illustrated in this example:
Using Assets
Trimming and Transcoding a Movie
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
15AVAsset *anAsset = <#Get an asset#>;
NSArray *compatiblePresets = [AVAssetExportSession
exportPresetsCompatibleWithAsset:anAsset];
if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) {
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]
initWithAsset:anAsset presetName:AVAssetExportPresetLowQuality];
// Implementation continues.
}
You complete configuration of the session by providing the output URL (The URL must be a file URL.)
AVAssetExportSession can infer the output file type from the URL’s path extension; typically, however,
you set it directly using outputFileType. You can also specify additional properties such as the time range,
a limit for the output file length, whether the exported file should be optimized for network use, and a video
composition. The following example illustrates how to use the timeRange property to trim the movie:
exportSession.outputURL = <#A file URL#>;
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
CMTime start = CMTimeMakeWithSeconds(1.0, 600);
CMTime duration = CMTimeMakeWithSeconds(3.0, 600);
CMTimeRange range = CMTimeRangeMake(start, duration);
exportSession.timeRange = range;
To create the new file you invoke exportAsynchronouslyWithCompletionHandler:. The completion
handler block is called when the export operation finishes; in your implementation of the handler, you should
check the session’s status to determine whether the export was successful, failed, or was canceled:
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([exportSession status]) {
case AVAssetExportSessionStatusFailed:
NSLog(@"Export failed: %@", [[exportSession error]
localizedDescription]);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"Export canceled");
Using Assets
Trimming and Transcoding a Movie
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
16break;
default:
break;
}
}];
You can cancel the export by sending the session a cancelExport message.
The export will fail if you try to overwrite an existing file, or write a file outside of the application’s sandbox. It
may also fail if:
● There is an incoming phone call
● Your application is in the background and another application starts playback
In these situations, you should typically inform the user that the export failed, then allow the user to restart
the export.
Reading and Writing Assets
You use an AVAssetReader when you want to perform an operation on the contents of an asset. For example,
you might read the audio track of an asset to produce a visual representation of the waveform. To produce an
asset from media such as sample buffers or still images, you use an AVAssetWriter object.
You can use an asset reader and asset writer object in tandem to convert an asset from one representation to
another. Using these objects you have more control over the conversion than you do with AVExportSession.
For example of you want to choose which of the tracks you want to be represented in the output file, specify
your own output format, or modify the asset during the conversion process.
Using Assets
Reading and Writing Assets
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
17To control the playback of assets, you use an AVPlayer object. During playback, you can use an AVPlayerItem
object to manage the presentation state of an asset as a whole, and an AVPlayerItemTrack to manage the
presentation state of an individual track. To display video, you use an AVPlayerLayer object.
Playing Assets
A player is a controller object that you use to manage playback of an asset, for example starting and stopping
playback, and seeking to a particular time. You use an instance of AVPlayer to play a single asset. On iOS 4.1
and later, you can use an AVQueuePlayer object to play a number of items in sequence (AVQueuePlayer is
a subclass of AVPlayer).
A player provides you with information about the state of the playback so, if you need to, you can synchronize
your user interface with the player’s state. You typically direct the output of a player to specialized Core
Animation Layer (an instance of AVPlayerLayer or AVSynchronizedLayer). To learn more about layers,
see Core Animation Programming Guide .
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
18
PlaybackMultiple player layers: You can create arbitrarily many AVPlayerLayer objects from a single
AVPlayer instance, but only the most-recently-created such layer will display any video content
on-screen.
Although ultimately you want to play an asset, you don’t provide assets directly to an AVPlayer object. Instead,
you provide an instance of AVPlayerItem. A player item manages the presentation state of an asset with
which it is associated. A player item contains player item tracks—instances of AVPlayerItemTrack—that
correspond to the tracks in the asset.
AVAsset
AVAssetTrack
AVAssetTrack
AVPlayerItem
AVPlayerItemTrack AVPlayer AVPlayerLayer
AVPlayerItemTrack
This abstraction means that you can play a given asset using different players simultaneously, but rendered
in different ways by each player. Using the item tracks, you can, for example, disable a particular track during
playback (you might not want to play the sound component).
AVAsset
AVPlayer 1 AVPlayer 2
• Video
• Audio R
• Audio L
AVPlayerItem 1 AVPlayerItem 2
AVPlayerItemTracks
time = 4:15 time = 2:10
Video
Audio R
Audio L
Off
Off
Playback
Playing Assets
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
19You can initialize a player item with an existing asset, or you can initialize a player item directly from a URL so
that you can play a resource at a particular location (AVPlayerItem will then create and configure an asset
for the resource). As with AVAsset, though, simply initializing a player item doesn’t necessarily mean it’s ready
for immediate playback. You can observe (using key-value observing) an item’s status property to determine
if and when it’s ready to play.
Handling Different Types of Asset
The way you configure an asset for playback may depend on the sort of asset you want to play. Broadly speaking,
there are two main types: file-based assets, to which you have random access (such as from a local file, the
camera roll, or the Media Library), and stream-based (HTTP Live Stream format).
To load and play a file-based asset. There are several steps to playing a file-based asset:
● Create an asset using AVURLAsset and load its tracks using
loadValuesAsynchronouslyForKeys:completionHandler:.
● When the asset has loaded its tracks, create an instance of AVPlayerItem using the asset.
● Associate the item with an instance of AVPlayer.
● Wait until the item’s status indicatesthat it’sready to play (typically you use key-value observing to receive
a notification when the status changes).
This approach is illustrated in “Putting it all Together: Playing a Video File Using AVPlayerLayer” (page 26).
To create and prepare an HTTP live stream for playback. Initialize an instance of AVPlayerItem using the
URL. (You cannot directly create an AVAsset instance to represent the media in an HTTP Live Stream.)
NSURL *url = [NSURL URLWithString:@"<#Live stream URL#>];
// You may find a test stream at
.
self.playerItem = [AVPlayerItem playerItemWithURL:url];
[playerItem addObserver:self forKeyPath:@"status" options:0
context:&ItemStatusContext];
self.player = [AVPlayer playerWithPlayerItem:playerItem];
When you associate the player item with a player, it starts to become ready to play. When it is ready to play,
the player item createsthe AVAsset and AVAssetTrack instances, which you can use to inspect the contents
of the live stream.
Playback
Handling Different Types of Asset
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
20If you simply want to play a live stream, you can take a shortcut and create a player directly using the URL:
self.player = [AVPlayer playerWithURL:<#Live stream URL#>];
[player addObserver:self forKeyPath:@"status" options:0
context:&PlayerStatusContext];
As with assets and items, initializing the player does not mean it’s ready for playback. You should observe the
player’s status property, which changes to AVPlayerStatusReadyToPlay when it is ready to play. You
can also observe the currentItem property to access the player item created for the stream.
If you don’t know what kind of URL you have. Follow these steps:
1. Try to initialize an AVURLAsset using the URL, then load its tracks key.
If the tracks load successfully, then you create a player item for the asset.
2. If 1 fails, create an AVPlayerItem directly from the URL.
Observe the player’s status property to determine whether it becomes playable.
If either route succeeds, you end up with a player item that you can then associate with a player.
Playing an Item
To start playback, you send a play message to the player.
- (IBAction)play:sender {
[player play];
}
In addition to simply playing, you can manage various aspects of the playback,such asthe rate and the location
of the playhead. You can also monitor the play state of the player; this is useful if you want to, for example,
synchronize the user interface to the presentation state of the asset—see “Monitoring Playback” (page 23).
Changing the Playback Rate
You change the rate of playback by setting the player’s rate property.
aPlayer.rate = 0.5;
aPlayer.rate = 2.0;
Playback
Playing an Item
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
21A value of 1.0 means “play at the natural rate of the current item”. Setting the rate to 0.0 is the same as pausing
playback—you can also use pause.
Seeking—Repositioning the Playhead
To move the playhead to a particular time, you generally use seekToTime:.
CMTime fiveSecondsIn = CMTimeMake(5, 1);
[player seekToTime:fiveSecondsIn];
The seekToTime: method, however, is tuned for performance rather than precision. If you need to move the
playhead precisely, instead you use seekToTime:toleranceBefore:toleranceAfter:.
CMTime fiveSecondsIn = CMTimeMake(5, 1);
[player seekToTime:fiveSecondsIn toleranceBefore:kCMTimeZero
toleranceAfter:kCMTimeZero];
Using a tolerance of zero may require the framework to decode a large amount of data. You should only use
zero if you are, for example, writing a sophisticated media editing application that requires precise control.
After playback, the player’s head is set to the end of the item, and further invocations of play have no effect.
To position the play head back at the beginning of the item, you can register to receive an
AVPlayerItemDidPlayToEndTimeNotification from the item. In the notification’s callback method, you
invoke seekToTime: with the argument kCMTimeZero.
// Register with the notification center after creating the player item.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:<#The player item#>];
- (void)playerItemDidReachEnd:(NSNotification *)notification {
[player seekToTime:kCMTimeZero];
}
Playback
Playing an Item
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
22Playing Multiple Items
On iOS 4.1 and later, you can use an AVQueuePlayer object to play a number of items in sequence.
AVQueuePlayer is a subclass of AVPlayer. You initialize a queue player with an array of player items:
NSArray *items = <#An array of player items#>;
AVQueuePlayer *queuePlayer = [[AVQueuePlayer alloc] initWithItems:items];
You can then play the queue using play, just as you would an AVPlayer object. The queue player plays each
item in turn. If you want to skip to the next item, you send the queue player an advanceToNextItem message.
You can modify the queue using insertItem:afterItem:, removeItem:, and removeAllItems. When
adding a new item, you should typically check whether it can be inserted into the queue, using
canInsertItem:afterItem:. You pass nil as the second argument to test whether the new item can be
appended to the queue:
AVPlayerItem *anItem = <#Get a player item#>;
if ([queuePlayer canInsertItem:anItem afterItem:nil]) {
[queuePlayer insertItem:anItem afterItem:nil];
}
Monitoring Playback
You can monitor a number of aspects of the presentation state of a player and the player item being played.
This is particularly useful for state changes that are not under your direct control, for example:
●
If the user uses multitasking to switch to a different application, a player’s rate property will drop to 0.0.
●
If you are playing remotemedia, a playeritem’s loadedTimeRanges and seekableTimeRanges properties
will change as more data becomes available.
These properties tell you what portions of the player item’s timeline are available.
● A player’s currentItem property changes as a player item is created for an HTTP live stream.
● A player item’s tracks property may change while playing an HTTP live stream.
This may happen if the stream offers different encodings for the content; the tracks change if the player
switches to a different encoding.
● A player or player item’s status may change if playback fails for some reason.
You can use key-value observing to monitor changes to values of these properties.
Playback
Playing Multiple Items
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
23Important: You should register for KVO change notifications and unregister from KVO change notifications
on the main thread. This avoids the possibility of receiving a partial notification if a change is being made
on another thread. AV Foundation invokes observeValueForKeyPath:ofObject:change:context:
on the main thread, even if the change operation is made on another thread.
Responding to a Change in Status
When a player or player item’s status changes, it emits a key-value observing change notification. If an object
is unable to play for some reason (for example, if the media services are reset), the status changes to
AVPlayerStatusFailed or AVPlayerItemStatusFailed as appropriate. In thissituation, the value of the
object’s error property is changed to an error object that describes why the object is no longer be able to
play.
AV Foundation does not specify what thread that the notification is sent on. If you want to update the user
interface, you must make sure that any relevant code is invoked on the main thread. This example uses
dispatch_async(3) OS X Developer Tools Manual Page to execute code on the main thread.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (context == <#Player status context#>) {
AVPlayer *thePlayer = (AVPlayer *)object;
if ([thePlayer status] == AVPlayerStatusFailed) {
NSError *error = [<#The AVPlayer object#> error];
// Respond to error: for example, display an alert sheet.
return;
}
// Deal with other status change if appropriate.
}
// Deal with other change notifications if appropriate.
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
return;
}
Playback
Monitoring Playback
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
24Tracking Readiness for Visual Display
You can observe an AVPlayerLayer object’s readyForDisplay property to be notified when the layer has
user-visible content. In particular, you might insert the player layer into the layer tree only when there is
something for the user to look at, and perform a transition from
Tracking Time
To track changes in the position of the playhead in an AVPlayer object, you can use
addPeriodicTimeObserverForInterval:queue:usingBlock: or
addBoundaryTimeObserverForTimes:queue:usingBlock:. You might do this to, for example, update
your user interface with information about time elapsed or time remaining, or perform some other user interface
synchronization.
● With addPeriodicTimeObserverForInterval:queue:usingBlock:,the block you provide isinvoked
at the interval you specify, and if time jumps, and when playback starts or stops.
● With addBoundaryTimeObserverForTimes:queue:usingBlock:, you pass an array of CMTimes
contained in NSValue objects. The block you provide is invoked whenever any of those times is traversed.
Both of the methods return an opaque object that serves as an observer. You must keep a strong reference to
the returned object as long as you want the time observation block to be invoked by the player. You must also
balance each invocation of these methods with a corresponding call to removeTimeObserver:.
With both of these methods, AV Foundation does not guarantee to invoke your block for every interval or
boundary passed. AV Foundation does not invoke a block if execution of a previously-invoked block has not
completed. You must make sure, therefore, that the work you perform in the block does not overly tax the
system.
// Assume a property: @property (strong) id playerObserver;
Float64 durationSeconds = CMTimeGetSeconds([<#An asset#> duration]);
CMTime firstThird = CMTimeMakeWithSeconds(durationSeconds/3.0, 1);
CMTime secondThird = CMTimeMakeWithSeconds(durationSeconds*2.0/3.0, 1);
NSArray *times = @[[NSValue valueWithCMTime:firstThird], [NSValue
valueWithCMTime:secondThird]];
self.playerObserver = [<#A player#> addBoundaryTimeObserverForTimes:times queue:NULL
usingBlock:^{
NSString *timeDescription = (NSString *)
Playback
Monitoring Playback
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
25CFBridgingRelease(CMTimeCopyDescription(NULL, [self.player currentTime]));
NSLog(@"Passed a boundary at %@", timeDescription);
}];
Reaching the End of an Item
You can register to receive an AVPlayerItemDidPlayToEndTimeNotification notification when a player
item has completed playback:
[[NSNotificationCenter defaultCenter] addObserver:<#The observer, typically self#>
selector:@selector(<#The selector name#>)
name:AVPlayerItemDidPlayToEndTimeNotification
object:<#A player item#>];
Putting it all Together: Playing a Video File Using AVPlayerLayer
This brief code example to illustrates how you can use an AVPlayer object to play a video file. It shows how
to:
● Configure a view to use an AVPlayerLayer layer
● Create an AVPlayer object
● Create an AVPlayerItem object for a file-based asset, and use key-value observing to observe its status
● Respond to the item becoming ready to play by enabling a button
● Play the item, then restore the player’s head to the beginning.
Note: To focus on the most relevant code, this example omits several aspects of a complete
application,such as memory management, and unregistering as an observer (for key-value observing
or for the notification center). To use AV Foundation, you are expected to have enough experience
with Cocoa to be able to infer the missing pieces.
For a conceptual introduction to playback, skip to “Playing Assets” (page 18).
Playback
Putting it all Together: Playing a Video File Using AVPlayerLayer
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
26The Player View
To play the visual component of an asset, you need a view containing an AVPlayerLayer layer to which the
output of an AVPlayer object can be directed. You can create a simple subclass of UIView to accommodate
this:
#import
#import
@interface PlayerView : UIView
@property (nonatomic) AVPlayer *player;
@end
@implementation PlayerView
+ (Class)layerClass {
return [AVPlayerLayer class];
}
- (AVPlayer*)player {
return [(AVPlayerLayer *)[self layer] player];
}
- (void)setPlayer:(AVPlayer *)player {
[(AVPlayerLayer *)[self layer] setPlayer:player];
}
@end
A Simple View Controller
Assume you have a simple view controller, declared as follows:
@class PlayerView;
@interface PlayerViewController : UIViewController
@property (nonatomic) AVPlayer *player;
@property (nonatomic) AVPlayerItem *playerItem;
@property (nonatomic, weak) IBOutlet PlayerView *playerView;
@property (nonatomic, weak) IBOutlet UIButton *playButton;
- (IBAction)loadAssetFromFile:sender;
Playback
Putting it all Together: Playing a Video File Using AVPlayerLayer
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
27- (IBAction)play:sender;
- (void)syncUI;
@end
The syncUI method synchronizes the button’s state with the player’s state:
- (void)syncUI {
if ((self.player.currentItem != nil) &&
([self.player.currentItem status] == AVPlayerItemStatusReadyToPlay)) {
self.playButton.enabled = YES;
}
else {
self.playButton.enabled = NO;
}
}
You can invoke syncUI in the view controller’s viewDidLoad method to ensure a consistent user interface
when the view is first displayed.
- (void)viewDidLoad {
[super viewDidLoad];
[self syncUI];
}
The other properties and methods are described in the remaining sections.
Creating the Asset
You create an asset from a URL using AVURLAsset. Creating the asset, however, does not necessarily mean
that it’s ready for use. To be used, an asset must have loaded its tracks. To avoid blocking the current thread,
you load the asset’s tracks asynchronously using
loadValuesAsynchronouslyForKeys:completionHandler:. (The following example assumes your
project contains a suitable video resource.)
- (IBAction)loadAssetFromFile:sender {
Playback
Putting it all Together: Playing a Video File Using AVPlayerLayer
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
28NSURL *fileURL = [[NSBundle mainBundle]
URLForResource:<#@"VideoFileName"#> withExtension:<#@"extension"#>];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
NSString *tracksKey = @"tracks";
[asset loadValuesAsynchronouslyForKeys:@[tracksKey] completionHandler:
^{
// The completion block goes here.
}];
}
In the completion block, you create an instance of AVPlayerItem for the asset, and set it as the player for
the player view. As with creating the asset, simply creating the player item does not mean it’s ready to use. To
determine when it’s ready to play, you can observe the item’s status. You trigger its preparation to play when
you associate it with the player.
// Define this constant for the key-value observation context.
static const NSString *ItemStatusContext;
// Completion handler block.
dispatch_async(dispatch_get_main_queue(),
^{
NSError *error;
AVKeyValueStatus status = [asset statusOfValueForKey:tracksKey
error:&error];
if (status == AVKeyValueStatusLoaded) {
self.playerItem = [AVPlayerItem playerItemWithAsset:asset];
[self.playerItem addObserver:self forKeyPath:@"status"
options:0 context:&ItemStatusContext];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
Playback
Putting it all Together: Playing a Video File Using AVPlayerLayer
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
29object:self.playerItem];
self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
[self.playerView setPlayer:self.player];
}
else {
// You should deal with the error appropriately.
NSLog(@"The asset's tracks were not loaded:\n%@", [error
localizedDescription]);
}
});
Responding to the Player Item’s Status Change
When the player item’s status changes, the view controller receives a key-value observing change notification.
AV Foundation does not specify what thread that the notification is sent on. If you want to update the user
interface, you must make sure that any relevant code is invoked on the main thread. This example uses
dispatch_async(3) OS X Developer Tools Manual Page to queue a message on the main thread
to synchronize the user interface.
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (context == &ItemStatusContext) {
dispatch_async(dispatch_get_main_queue(),
^{
[self syncUI];
});
return;
}
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
return;
}
Playback
Putting it all Together: Playing a Video File Using AVPlayerLayer
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
30Playing the Item
Playing the item is trivial: you send a play message to the player.
- (IBAction)play:sender {
[player play];
}
This only playsthe item once, though. After playback, the player’s head isset to the end of the item, and further
invocations of play will have no effect. To position the play head back at the beginning of the item, you can
register to receive an AVPlayerItemDidPlayToEndTimeNotification from the item. In the notification’s
callback method, invoke seekToTime: with the argument kCMTimeZero.
// Register with the notification center after creating the player item.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[self.player currentItem]];
- (void)playerItemDidReachEnd:(NSNotification *)notification {
[self.player seekToTime:kCMTimeZero];
}
Playback
Putting it all Together: Playing a Video File Using AVPlayerLayer
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
31To manage the capture from a device such as a camera or microphone, you assemble objects to represent
inputs and outputs, and use an instance of AVCaptureSession to coordinate the data flow between them.
Minimally you need:
● An instance of AVCaptureDevice to represent the input device, such as a camera or microphone
● An instance of a concrete subclass of AVCaptureInput to configure the ports from the input device
● An instance of a concrete subclass of AVCaptureOutput to manage the output to a movie file or still
image
● An instance of AVCaptureSession to coordinate the data flow from the input to the output
To show the user what a camera is recording, you can use an instance of AVCaptureVideoPreviewLayer
(a subclass of CALayer).
You can configure multiple inputs and outputs, coordinated by a single session:
AVCapture Device Input AVCapture Device Input
AVCaptureMovieFileOutput AVCaptureStillImageOutput
AVCaptureVideoPreviewLayer
AVCapture Session Capture Session
For many applications, thisis as much detail as you need. Forsome operations, however, (if you want to monitor
the power levels in an audio channel, for example) you need to consider how the various ports of an input
device are represented, how those ports are connected to the output.
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
32
Media CaptureA connection between a capture input and a capture output in a capture session is represented by an
AVCaptureConnection object. Capture inputs(instances of AVCaptureInput) have one or more input ports
(instances of AVCaptureInputPort). Capture outputs (instances of AVCaptureOutput) can accept data
from one or more sources (for example, an AVCaptureMovieFileOutput object accepts both video and
audio data).
When you add an input or an output to a session, the session “greedily” forms connections between all the
compatible capture inputs’ ports and capture outputs. A connection between a capture input and a capture
output is represented by an AVCaptureConnection object.
Capture Device Input
Capture connection Capture connection
Capture input port
(Video)
Capture input port
(Audio)
Capture Device Input
Capture input port
(Audio)
Connections
AVCaptureMovieFileOutput AVCaptureStillImageOutput
Connections
Capture Session
Capture connection
You can use a capture connection to enable or disable the flow of data from a given input or to a given output.
You can also use a connection to monitor the average and peak power levels in an audio channel.
Use a Capture Session to Coordinate Data Flow
AVCaptureSession object is the central coordinating object you use to manage data capture. You use an
instance to coordinate the flow of data from AV input devices to outputs. You add the capture devices and
outputs you want to the session, then start data flow by sending the session a startRunning message, and
stop recording by sending a stopRunning message.
AVCaptureSession *session = [[AVCaptureSession alloc] init];
// Add inputs and outputs.
[session startRunning];
Media Capture
Use a Capture Session to Coordinate Data Flow
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
33Configuring a Session
You use a preset on the session to specify the image quality and resolution you want. A preset is a constant
that identifies one of a number of possible configurations; in some cases the actual configuration is
device-specific:
Symbol Resolution Comments
Highest recording quality.
This varies per device.
AVCaptureSessionPresetHigh High
Suitable for WiFi sharing.
The actual values may change.
AVCaptureSessionPresetMedium Medium
Suitable for 3G sharing.
The actual values may change.
AVCaptureSessionPresetLow Low
AVCaptureSessionPreset640x480 640x480 VGA.
AVCaptureSessionPreset1280x720 1280x720 720p HD.
Full photo resolution.
This is not supported for video output.
AVCaptureSessionPresetPhoto Photo
For examples of the actual valuesthese presetsrepresent for various devices,see “Saving to a Movie File” (page
43) and “Capturing Still Images” (page 47).
If you want to set a size-specific configuration, you should check whether it is supported before setting it:
if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
session.sessionPreset = AVCaptureSessionPreset1280x720;
}
else {
// Handle the failure.
}
In many situations, you create a session and the various inputs and outputs all at once. Sometimes, however,
you may want to reconfigure a running session, perhaps as different input devices become available, or in
response to user request. This can present a challenge, since, if you change them one at a time, a new setting
may be incompatible with an existing setting. To deal with this, you use beginConfiguration and
commitConfiguration to batch multiple configuration operations into an atomic update. After calling
Media Capture
Use a Capture Session to Coordinate Data Flow
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
34beginConfiguration, you can for example add or remove outputs, alter the sessionPreset, or configure
individual capture input or output properties. No changes are actually made until you invoke
commitConfiguration, at which time they are applied together.
[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];
Monitoring Capture Session State
A capture session posts notifications that you can observe to be notified, for example, when it starts or stops
running, or when it is interrupted. You can also register to receive an
AVCaptureSessionRuntimeErrorNotification if a runtime error occurs. You can also interrogate the
session’s running property to find out if it is running, and its interrupted property to find out if it is
interrupted.
An AVCaptureDevice Object Represents an Input Device
An AVCaptureDevice object abstracts a physical capture device that provides input data (such as audio or
video) to an AVCaptureSession object. There is one object for each input device, so for example on an
iPhone 3GS there is one video input for the camera and one audio input for the microphone; on an iPhone 4
there are two video inputs—one for front-facing the camera, one for the back-facing camera—and one audio
input for the microphone.
You can find out what capture devices are currently available using the AVCaptureDevice class methods
devices and devicesWithMediaType:, and if necessary find out what featuresthe devices offer (see “Device
Capture Settings” (page 36)). The list of available devices may change, though. Current devices may become
unavailable (if they’re used by another application), and new devices may become available, (if they’re
relinquished by another application). You should register to receive
AVCaptureDeviceWasConnectedNotification and
AVCaptureDeviceWasDisconnectedNotificationnotifications to be alerted when the list of available
devices changes.
You add a device to a capture session using a capture input (see “Use Capture Inputs to Add a Capture Device
to a Session” (page 41)).
Media Capture
An AVCaptureDevice Object Represents an Input Device
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
35Device Characteristics
You can ask a device about several different characteristics. You can test whether it provides a particular media
type or supports a given capture session preset using hasMediaType: and
supportsAVCaptureSessionPreset: respectively. To provide information to the user, you can find out
the position of the capture device (whether it is on the front or the back of the unit they’re using), and its
localized name. This may be useful if you want to present a list of capture devices to allow the user to choose
one.
The following code example iterates over all the available devices and logs their name, and for video devices
their position on the unit.
NSArray *devices = [AVCaptureDevice devices];
for (AVCaptureDevice *device in devices) {
NSLog(@"Device name: %@", [device localizedName]);
if ([device hasMediaType:AVMediaTypeVideo]) {
if ([device position] == AVCaptureDevicePositionBack) {
NSLog(@"Device position : back");
}
else {
NSLog(@"Device position : front");
}
}
}
In addition, you can find out the device’s model ID and its unique ID.
Device Capture Settings
Different devices have different capabilities; for example, some may support different focus or flash modes;
some may support focus on a point of interest.
Feature iPhone 3G iPhone 3GS iPhone 4 (Back) iPhone 4 (Front)
Focus mode NO YES YES NO
Media Capture
An AVCaptureDevice Object Represents an Input Device
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
36Feature iPhone 3G iPhone 3GS iPhone 4 (Back) iPhone 4 (Front)
Focus point of interest NO YES YES NO
Exposure mode YES YES YES YES
Exposure point of interest NO YES YES YES
White balance mode YES YES YES YES
Flash mode NO NO YES NO
Torch mode NO NO YES NO
The following code fragment shows how you can find video input devices that have a torch mode and support
a given capture session preset:
NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
NSMutableArray *torchDevices = [[NSMutableArray alloc] init];
for (AVCaptureDevice *device in devices) {
[if ([device hasTorch] &&
[device supportsAVCaptureSessionPreset:AVCaptureSessionPreset640x480]) {
[torchDevices addObject:device];
}
}
If you find multiple devices that meet your criteria, you might let the user choose which one they want to use.
To display a description of a device to the user, you can use its localizedName property.
You use the various different features in similar ways. There are constants to specify a particular mode, and
you can ask a device whether it supports a particular mode. In several cases you can observe a property to be
notified when a feature is changing. In all cases, you should lock the device before changing the mode of a
particular feature, as described in “Configuring a Device” (page 40).
Note: Focus point of interest and exposure point of interest are mutually exclusive, as are focus
mode and exposure mode.
Focus modes
There are three focus modes:
Media Capture
An AVCaptureDevice Object Represents an Input Device
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
37● AVCaptureFocusModeLocked: the focal length is fixed.
This is useful when you want to allow the user to compose a scene then lock the focus.
● AVCaptureFocusModeAutoFocus: the camera does a single scan focus then reverts to locked.
This is suitable for a situation where you want to select a particular item on which to focus and then
maintain focus on that item even if it is not the center of the scene.
● AVCaptureFocusModeContinuousAutoFocus: the camera continuously auto-focuses as needed.
You use the isFocusModeSupported: method to determine whether a device supports a given focus mode,
then set the mode using the focusMode property.
In addition, a device may support a focus point of interest. You test for support using
focusPointOfInterestSupported. If it’ssupported, you set the focal point using focusPointOfInterest.
You pass a CGPoint where {0,0} representsthe top left of the picture area, and {1,1} representsthe bottom
right in landscape mode with the home button on the right—this applies even if the device is in portrait mode.
You can use the adjustingFocus property to determine whether a device is currently focusing. You can
observe the property using key-value observing to be notified when a device starts and stops focusing.
If you change the focus mode settings, you can return them to the default configuration as follows:
if ([currentDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
CGPoint autofocusPoint = CGPointMake(0.5f, 0.5f);
[currentDevice setFocusPointOfInterest:autofocusPoint];
[currentDevice setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
}
Exposure modes
There are two exposure modes:
● AVCaptureExposureModeLocked: the exposure mode is fixed.
● AVCaptureExposureModeAutoExpose: the camera continuously changesthe exposure level as needed.
You use the isExposureModeSupported: method to determine whether a device supports a given exposure
mode, then set the mode using the exposureMode property.
Media Capture
An AVCaptureDevice Object Represents an Input Device
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
38In addition, a device may support an exposure point of interest. You test for support using
exposurePointOfInterestSupported. If it’s supported, you set the exposure point using
exposurePointOfInterest. You pass a CGPoint where {0,0} represents the top left of the picture area,
and {1,1} represents the bottom right in landscape mode with the home button on the right—this applies
even if the device is in portrait mode.
You can use the adjustingExposure property to determine whether a device is currently changing its
exposure setting. You can observe the property using key-value observing to be notified when a device starts
and stops changing its exposure setting.
If you change the exposure settings, you can return them to the default configuration as follows:
if ([currentDevice
isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
CGPoint exposurePoint = CGPointMake(0.5f, 0.5f);
[currentDevice setExposurePointOfInterest:exposurePoint];
[currentDevice setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
}
Flash modes
There are three flash modes:
● AVCaptureFlashModeOff: the flash will never fire.
● AVCaptureFlashModeOn: the flash will always fire.
● AVCaptureFlashModeAuto: the flash will fire if needed.
You use hasFlash to determine whether a device has a flash. You use the isFlashModeSupported: method
to determine whether a device supports a given flash mode, then set the mode using the flashMode property.
Torch mode
Torch mode is where a camera uses the flash continuously at a low power to illuminate a video capture. There
are three torch modes:
● AVCaptureTorchModeOff: the torch is always off.
● AVCaptureTorchModeOn: the torch is always on.
● AVCaptureTorchModeAuto: the torch is switched on and off as needed.
Media Capture
An AVCaptureDevice Object Represents an Input Device
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
39You use hasTorch to determine whether a device has a flash. You use the isTorchModeSupported: method
to determine whether a device supports a given flash mode, then set the mode using the torchMode property.
For devices with a torch, the torch only turns on if the device is associated with a running capture session.
White balance
There are two white balance modes:
● AVCaptureWhiteBalanceModeLocked: the white balance mode is fixed.
● AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance: the camera continuously changes
the white balance as needed.
You use the isWhiteBalanceModeSupported: method to determine whether a device supports a given
white balance mode, then set the mode using the whiteBalanceMode property.
You can use the adjustingWhiteBalance property to determine whether a device is currently changing its
white balance setting. You can observe the property using key-value observing to be notified when a device
starts and stops changing its white balance setting.
Configuring a Device
To set capture properties on a device, you must first acquire a lock on the device using
lockForConfiguration:. This avoids making changes that may be incompatible with settings in other
applications. The following code fragment illustrates how to approach changing the focus mode on a device
by first determining whether the mode is supported, then attempting to lock the device for reconfiguration.
The focus mode is changed only if the lock is obtained, and the lock is released immediately afterward.
if ([device isFocusModeSupported:AVCaptureFocusModeLocked]) {
NSError *error = nil;
if ([device lockForConfiguration:&error]) {
device.focusMode = AVCaptureFocusModeLocked;
[device unlockForConfiguration];
}
else {
// Respond to the failure as appropriate.
You should only hold the device lock if you need settable device properties to remain unchanged. Holding
the device lock unnecessarily may degrade capture quality in other applications sharing the device.
Media Capture
An AVCaptureDevice Object Represents an Input Device
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
40Switching Between Devices
Sometimes you may want to allow the user to switch between input devices—for example, on an iPhone 4
they could switch from using the front to the back camera. To avoid pauses or stuttering, you can reconfigure
a session while it is running, however you should use beginConfiguration and commitConfiguration
to bracket your configuration changes:
AVCaptureSession *session = <#A capture session#>;
[session beginConfiguration];
[session removeInput:frontFacingCameraDeviceInput];
[session addInput:backFacingCameraDeviceInput];
[session commitConfiguration];
When the outermost commitConfiguration is invoked, all the changes are made together. This ensures a
smooth transition.
Use Capture Inputs to Add a Capture Device to a Session
To add a capture device to a capture session, you use an instance of AVCaptureDeviceInput (a concrete
subclass of the abstract AVCaptureInput class). The capture device input manages the device’s ports.
NSError *error;
AVCaptureDeviceInput *input =
[AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
// Handle the error appropriately.
}
You add inputs to a session using addInput:. If appropriate, you can check whether a capture input is
compatible with an existing session using canAddInput:.
AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureDeviceInput *captureDeviceInput = <#Get a capture device input#>;
if ([captureSession canAddInput:captureDeviceInput]) {
[captureSession addInput:captureDeviceInput];
Media Capture
Use Capture Inputs to Add a Capture Device to a Session
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
41}
else {
// Handle the failure.
}
See “Configuring a Session” (page 34) for more details on how you might reconfigure a running session.
An AVCaptureInput vends one or more streams of media data. For example, input devices can provide both
audio and video data. Each media stream provided by an input is represented by an AVCaptureInputPort
object. A capture session uses an AVCaptureConnection object to define the mapping between a set of
AVCaptureInputPort objects and a single AVCaptureOutput.
Use Capture Outputs to Get Output from a Session
To get output from a capture session, you add one or more outputs. An output is an instance of a concrete
subclass of AVCaptureOutput; you use:
● AVCaptureMovieFileOutput to output to a movie file
● AVCaptureVideoDataOutput if you want to process frames from the video being captured
● AVCaptureAudioDataOutput if you want to process the audio data being captured
● AVCaptureStillImageOutput if you want to capture still images with accompanying metadata
You add outputs to a capture session using addOutput:. You check whether a capture output is compatible
with an existing session using canAddOutput:. You can add and remove outputs as you want while the
session is running.
AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureMovieFileOutput *movieInput = <#Create and configure a movie output#>;
if ([captureSession canAddOutput:movieInput]) {
[captureSession addOutput:movieInput];
}
else {
// Handle the failure.
}
Media Capture
Use Capture Outputs to Get Output from a Session
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
42Saving to a Movie File
You save movie data to a file using an AVCaptureMovieFileOutput object. (AVCaptureMovieFileOutput
is a concrete subclass of AVCaptureFileOutput, which defines much of the basic behavior.) You can configure
various aspects of the movie file output, such as the maximum duration of the recording, or the maximum file
size. You can also prohibit recording if there is less than a given amount of disk space left.
AVCaptureMovieFileOutput *aMovieFileOutput = [[AVCaptureMovieFileOutput alloc]
init];
CMTime maxDuration = <#Create a CMTime to represent the maximum duration#>;
aMovieFileOutput.maxRecordedDuration = maxDuration;
aMovieFileOutput.minFreeDiskSpaceLimit = <#An appropriate minimum given the quality
of the movie format and the duration#>;
The resolution and bit rate for the output depend on the capture session’s sessionPreset. The video encoding
is typically H.264 and audio encoding AAC. The actual values vary by device, as illustrated in the following
table.
Preset iPhone 3G iPhone 3GS iPhone 4 (Back) iPhone 4 (Front)
640x480
3.5 mbps
1280x720
10.5 mbps
640x480
3.5 mbps
No video
Apple Lossless
High
480x360
700 kbps
480x360
700 kbps
480x360
700 kbps
No video
Apple Lossless
Medium
192x144
128 kbps
192x144
128 kbps
192x144
128 kbps
No video
Apple Lossless
Low
640x480
3.5 mbps
640x480
3.5 mbps
640x480
3.5 mbps
No video
Apple Lossless
640x480
No video
64 kbps AAC
No video
64 kbps AAC
No video
64 kbps AAC
No video
Apple Lossless
1280x720
Notsupported for
video output
Notsupported for
video output
Not supported
for video output
Not supported for
video output
Photo
Media Capture
Use Capture Outputs to Get Output from a Session
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
43Starting a Recording
You start recording a QuickTime movie using startRecordingToOutputFileURL:recordingDelegate:.
You need to supply a file-based URL and a delegate. The URL must not identify an existing file, as the movie
file output does not overwrite existing resources. You must also have permission to write to the specified
location. The delegate must conform to the AVCaptureFileOutputRecordingDelegate protocol, and
must implement the
captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error: method.
AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie file output#>;
NSURL *fileURL = <#A file URL that identifies the output location#>;
[aMovieFileOutput startRecordingToOutputFileURL:fileURL recordingDelegate:<#The
delegate#>];
In the implementation of
captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error:, the delegate
might write the resulting movie to the camera roll. Itshould also check for any errorsthat might have occurred.
Ensuring the File Was Written Successfully
To determine whether the file was saved successfully, in the implementation of
captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error: you check
not only the error, but also the value of the AVErrorRecordingSuccessfullyFinishedKey in the error’s
user info dictionary:
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput
didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
fromConnections:(NSArray *)connections
error:(NSError *)error {
BOOL recordedSuccessfully = YES;
if ([error code] != noErr) {
// A problem occurred: Find out if the recording was successful.
id value = [[error userInfo]
objectForKey:AVErrorRecordingSuccessfullyFinishedKey];
if (value) {
recordedSuccessfully = [value boolValue];
}
Media Capture
Use Capture Outputs to Get Output from a Session
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
44}
// Continue as appropriate...
You should check the value of the AVErrorRecordingSuccessfullyFinishedKey in the error’s user info
dictionary because the file might have been saved successfully, even though you got an error. The error might
indicate that one of your recording constraints wasreached, for example AVErrorMaximumDurationReached
or AVErrorMaximumFileSizeReached. Other reasons the recording might stop are:
● The disk is full—AVErrorDiskFull.
● The recording device was disconnected (for example, the microphone was removed from an iPod
touch)—AVErrorDeviceWasDisconnected.
● The session wasinterrupted (for example, a phone call wasreceived)—AVErrorSessionWasInterrupted.
Adding Metadata to a File
You can set metadata for the movie file at any time, even while recording. This is useful for situations where
the information is not available when the recording starts, as may be the case with location information.
Metadata for a file output is represented by an array of AVMetadataItem objects; you use an instance of its
mutable subclass, AVMutableMetadataItem, to create metadata of your own.
AVCaptureMovieFileOutput *aMovieFileOutput = <#Get a movie file output#>;
NSArray *existingMetadataArray = aMovieFileOutput.metadata;
NSMutableArray *newMetadataArray = nil;
if (existingMetadataArray) {
newMetadataArray = [existingMetadataArray mutableCopy];
}
else {
newMetadataArray = [[NSMutableArray alloc] init];
}
AVMutableMetadataItem *item = [[AVMutableMetadataItem alloc] init];
item.keySpace = AVMetadataKeySpaceCommon;
item.key = AVMetadataCommonKeyLocation;
CLLocation *location - <#The location to set#>;
item.value = [NSString stringWithFormat:@"%+08.4lf%+09.4lf/"
location.coordinate.latitude, location.coordinate.longitude];
Media Capture
Use Capture Outputs to Get Output from a Session
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
45[newMetadataArray addObject:item];
aMovieFileOutput.metadata = newMetadataArray;
Processing Frames of Video
An AVCaptureVideoDataOutput object uses delegation to vend video frames. You set the delegate using
setSampleBufferDelegate:queue:. In addition to the delegate, you specify a serial queue on which they
delegate methods are invoked. You must use a serial queue to ensure that frames are delivered to the delegate
in the proper order. You should not pass the queue returned by dispatch_get_current_queue since there
is no guarantee as to which thread the current queue is running on. You can use the queue to modify the
priority given to delivering and processing the video frames.
The frames are presented in the delegate method,
captureOutput:didOutputSampleBuffer:fromConnection:, as instances of the CMSampleBuffer
opaque type (see “Representations of Media” (page 58)). By default, the buffers are emitted in the camera’s
most efficient format. You can use the videoSettings property to specify a custom output format. The video
settings property is a dictionary; currently, the only supported key is kCVPixelBufferPixelFormatTypeKey.
The recommended pixel format choices for iPhone 4 are
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange or kCVPixelFormatType_32BGRA; for iPhone
3G the recommended pixel format choices are kCVPixelFormatType_422YpCbCr8 or
kCVPixelFormatType_32BGRA. Both Core Graphics and OpenGL work well with the BGRA format:
AVCaptureSession *captureSession = <#Get a capture session#>;
NSDictionary *newSettings =
@{ (NSString *)kCVPixelBufferPixelFormatTypeKey :
@(kCVPixelFormatType_32BGRA) };
captureSession.videoSettings = newSettings;
Performance Considerations for Processing Video
You should set the session output to the lowest practical resolution for your application. Setting the output
to a higher resolution than necessary wastes processing cycles and needlessly consumes power.
You must ensure that your implementation of
captureOutput:didOutputSampleBuffer:fromConnection: is able to process a sample buffer within
the amount of time allotted to a frame. If it takes too long, and you hold onto the video frames, AV Foundation
will stop delivering frames, not only to your delegate but also other outputs such as a preview layer.
Media Capture
Use Capture Outputs to Get Output from a Session
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
46You can use the capture video data output’s minFrameDuration property to ensure you have enough time
to process a frame—at the cost of having a lower frame rate than would otherwise be the case. You might
also ensure that the alwaysDiscardsLateVideoFrames property is set to YES (the default). This ensures
that any late video frames are dropped rather than handed to you for processing. Alternatively, if you are
recording and it doesn’t matter if the output fames are a little late, you would prefer to get all of them, you
can set the property value to NO. This does not mean that frames will not be dropped (that is, frames may still
be dropped), but they may not be dropped as early, or as efficiently.
Capturing Still Images
You use an AVCaptureStillImageOutput output if you want to capture still images with accompanying
metadata. The resolution of the image depends on the preset for the session, as illustrated in this table:
Preset iPhone 3G iPhone 3GS iPhone 4 (Back) iPhone 4 (Front)
High 400x304 640x480 1280x720 640x480
Medium 400x304 480x360 480x360 480x360
Low 400x304 192x144 192x144 192x144
640x480 N/A 640x480 640x480 640x480
1280x720 N/A N/A 1280x720 N/A
Photo 1600x1200 2048x1536 2592x1936 640x480
Pixel and Encoding Formats
Different devices support different image formats:
iPhone 3G iPhone 3GS iPhone 4
yuvs, 2vuy, BGRA, jpeg 420f, 420v, BGRA, jpeg 420f, 420v, BGRA, jpeg
You can find out what pixel and codec types are supported using availableImageDataCVPixelFormatTypes
and availableImageDataCodecTypes respectively. You set the outputSettings dictionary to specify
the image format you want, for example:
AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc]
init];
NSDictionary *outputSettings = @{ AVVideoCodecKey : AVVideoCodecJPEG};
[stillImageOutput setOutputSettings:outputSettings];
Media Capture
Use Capture Outputs to Get Output from a Session
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
47If you want to capture a JPEG image, you should typically not specify your own compression format. Instead,
you should let the still image output do the compression for you,since its compression is hardware-accelerated.
If you need a data representation of the image, you can use jpegStillImageNSDataRepresentation: to
get an NSData object without re-compressing the data, even if you modify the image’s metadata.
Capturing an Image
When you want to capture an image, you send the output a
captureStillImageAsynchronouslyFromConnection:completionHandler: message. The first
argument is the connection you want to use for the capture. You need to look for the connection whose input
port is collecting video:
AVCaptureConnection *videoConnection = nil;
for (AVCaptureConnection *connection in stillImageOutput.connections) {
for (AVCaptureInputPort *port in [connection inputPorts]) {
if ([[port mediaType] isEqual:AVMediaTypeVideo] ) {
videoConnection = connection;
break;
}
}
if (videoConnection) { break; }
}
The second argument to captureStillImageAsynchronouslyFromConnection:completionHandler:
is a block that takes two arguments: a CMSampleBuffer containing the image data, and an error. The sample
buffer itself may contain metadata,such as an Exif dictionary, as an attachment. You can modify the attachments
should you want, but note the optimization for JPEG images discussed in “Pixel and Encoding Formats” (page
47).
[stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection
completionHandler:
^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
CFDictionaryRef exifAttachments =
CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary,
NULL);
if (exifAttachments) {
// Do something with the attachments.
}
Media Capture
Use Capture Outputs to Get Output from a Session
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
48// Continue as appropriate.
}];
Showing the User What’s Being Recorded
You can provide the user with a preview of what’s being recorded by the camera using a preview layer, or by
the microphone by monitoring the audio channel.
Video Preview
You can provide the user with a preview of what’s being recorded using an AVCaptureVideoPreviewLayer
object. AVCaptureVideoPreviewLayer is a subclass ofCALayer (see Core Animation Programming Guide .
You don’t need any outputs to show the preview.
Unlike a capture output, a video preview layer maintains a strong reference to the session with which it is
associated. This is to ensure that the session is not deallocated while the layer is attempting to display video.
This is reflected in the way you initialize a preview layer:
AVCaptureSession *captureSession = <#Get a capture session#>;
CALayer *viewLayer = <#Get a layer from the view in which you want to present the
preview#>;
AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer
alloc] initWithSession:captureSession];
[viewLayer addSublayer:captureVideoPreviewLayer];
In general, the preview layer behaves like any other CALayer object in the render tree (see Core Animation
Programming Guide ). You can scale the image and perform transformations, rotations and so on just as you
would any layer. One difference is that you may need to set the layer’s orientation property to specify how
itshould rotate images coming from the camera. In addition, on iPhone 4 the preview layersupports mirroring
(this is the default when previewing the front-facing camera).
Video Gravity Modes
The preview layer supports three gravity modes that you set using videoGravity:
● AVLayerVideoGravityResizeAspect: This preserves the aspect ratio, leaving black bars where the
video does not fill the available screen area.
Media Capture
Showing the User What’s Being Recorded
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
49● AVLayerVideoGravityResizeAspectFill: This preservesthe aspect ratio, but fillsthe available screen
area, cropping the video when necessary.
● AVLayerVideoGravityResize: This simply stretches the video to fill the available screen area, even if
doing so distorts the image.
Using “Tap to Focus” With a Preview
You need to take care when implementing tap-to-focus in conjunction with a preview layer. You must account
for the preview orientation and gravity of the layer, and the possibility that the preview may be mirrored.
Showing Audio Levels
To monitor the average and peak power levels in an audio channel in a capture connection, you use an
AVCaptureAudioChannel object. Audio levels are not key-value observable, so you must poll for updated
levels as often as you want to update your user interface (for example, 10 times a second).
AVCaptureAudioDataOutput *audioDataOutput = <#Get the audio data output#>;
NSArray *connections = audioDataOutput.connections;
if ([connections count] > 0) {
// There should be only one connection to an AVCaptureAudioDataOutput.
AVCaptureConnection *connection = [connections objectAtIndex:0];
NSArray *audioChannels = connection.audioChannels;
for (AVCaptureAudioChannel *channel in audioChannels) {
float avg = channel.averagePowerLevel;
float peak = channel.peakHoldLevel;
// Update the level meter user interface.
}
}
Putting it all Together: Capturing Video Frames as UIImage Objects
This brief code example to illustrates how you can capture video and convert the frames you get to UIImage
objects. It shows you how to:
● Create an AVCaptureSession object to coordinate the flow of data from an AV input device to an output
Media Capture
Putting it all Together: Capturing Video Frames as UIImage Objects
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
50● Find the AVCaptureDevice object for the input type you want
● Create an AVCaptureDeviceInput object for the device
● Create an AVCaptureVideoDataOutput object to produce video frames
●
Implement a delegate for the AVCaptureVideoDataOutput object to process video frames
●
Implement a function to convert the CMSampleBuffer received by the delegate into a UIImage object
Note: To focus on the most relevant code, this example omits several aspects of a complete
application, including memory management. To use AV Foundation, you are expected to have
enough experience with Cocoa to be able to infer the missing pieces.
Create and Configure a Capture Session
You use an AVCaptureSession object to coordinate the flow of data from an AV input device to an output.
Create a session, and configure it to produce medium resolution video frames.
AVCaptureSession *session = [[AVCaptureSession alloc] init];
session.sessionPreset = AVCaptureSessionPresetMedium;
Create and Configure the Device and Device Input
Capture devices are represented by AVCaptureDevice objects; the class provides methods to retrieve an
object for the input type you want. A device has one or more ports, configured using an AVCaptureInput
object. Typically, you use the capture input in its default configuration.
Find a video capture device, then create a device input with the device and add it to the session.
AVCaptureDevice *device =
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
AVCaptureDeviceInput *input =
[AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
// Handle the error appropriately.
}
[session addInput:input];
Media Capture
Putting it all Together: Capturing Video Frames as UIImage Objects
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
51Create and Configure the Data Output
You use an AVCaptureVideoDataOutput object to process uncompressed frames from the video being
captured. You typically configure several aspects of an output. For video, for example, you can specify the pixel
format using the videoSettings property, and cap the frame rate by setting the minFrameDuration
property.
Create and configure an output for video data and add it to the session; cap the frame rate to 15 fps by setting
the minFrameDuration property to 1/15 second:
AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
[session addOutput:output];
output.videoSettings =
@{ (NSString *)kCVPixelBufferPixelFormatTypeKey :
@(kCVPixelFormatType_32BGRA) };
output.minFrameDuration = CMTimeMake(1, 15);
The data output object uses delegation to vend the video frames. The delegate must adopt the
AVCaptureVideoDataOutputSampleBufferDelegate protocol. When you set the data output’s delegate,
you must also provide a queue on which callbacks should be invoked.
dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
[output setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);
You use the queue to modify the priority given to delivering and processing the video frames.
Implement the Sample Buffer Delegate Method
In the delegate class, implement the method
(captureOutput:didOutputSampleBuffer:fromConnection:) that is called when a sample buffer is
written. The video data output object delivers frames as CMSampleBuffers, so you need to convert from the
CMSampleBuffer to a UIImage object. The function for this operation isshown in “Converting a CMSampleBuffer
to a UIImage” (page 59).
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
Media Capture
Putting it all Together: Capturing Video Frames as UIImage Objects
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
52UIImage *image = imageFromSampleBuffer(sampleBuffer);
// Add your code here that uses the image.
}
Remember that the delegate method is invoked on the queue you specified in
setSampleBufferDelegate:queue:; if you want to update the user interface, you must invoke any relevant
code on the main thread.
Starting and Stopping Recording
After configuring the capture session, you send it a startRunning message to start the recording.
[session startRunning];
To stop recording, you send the session a stopRunning message.
Media Capture
Putting it all Together: Capturing Video Frames as UIImage Objects
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
53Time-based audio-visual data such as a movie file or a video stream is represented in the AV Foundation
framework by AVAsset. Its structure dictates much of the framework works. Several low-level data structures
that AV Foundation uses to represent time and media such as sample buffers come from the Core Media
framework.
Representation of Assets
AVAsset is the core class in the AV Foundation framework. It provides a format-independent abstraction of
time-based audiovisual data, such as a movie file or a video stream. In many cases, you work with one of its
subclasses: you use the composition subclasses when you create new assets (see “Editing” (page 7)), and you
use AVURLAsset to create a new asset instance from media at a given URL (including assetsfrom the MPMedia
framework or the Asset Library framework—see “Using Assets” (page 9)).
AVURLAsset
AVMutableComposition
AVComposition
AVAsset
NSObject
An asset contains a collection of tracks that are intended to be presented or processed together, each of a
uniform media type, including (but not limited to) audio, video, text, closed captions, and subtitles. The asset
object providesinformation about whole resource,such asits duration or title, as well as hintsfor presentation,
such as its natural size. Assets may also have metadata, represented by instances of AVMetadataItem.
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
54
Time and Media RepresentationsA track is represented by an instance of AVAssetTrack. In a typical simple case, one track represents the
audio component and another represents the video component; in a complex composition, there may be
multiple overlapping tracks of audio and video.
AVAsset
AVMetadataItem
AVMetadataItem
AVAssetTrack
AVAssetTrack AVAssetTrack AVAssetTrack
A track has a number of properties, such as its type (video or audio), visual and/or audible characteristics (as
appropriate), metadata, and timeline (expressed in terms of its parent asset). A track also has an array of format
descriptions. The array contains CMFormatDescriptions (see CMFormatDescriptionRef), each of which
describes the format of media samples referenced by the track. A track that contains uniform media (for
example, all encoded using to the same settings) will provide an array with a count of 1.
A track may itself be divided into segments, represented by instances of AVAssetTrackSegment. A segment
is a time mapping from the source to the asset track timeline.
Representations of Time
Time in AV Foundation is represented by primitive structures from the Core Media framework.
CMTime Represents a Length of Time
CMTime is a C structure that represents time as a rational number, with a numerator (an int64_t value), and
a denominator (an int32_t timescale).Conceptually, the timescale specifies the fraction of a second each unit
in the numerator occupies. Thusif the timescale is 4, each unit represents a quarter of a second; if the timescale
is 10, each unit represents a tenth of a second, and so on. You frequently use a timescale of 600, since this is
a common multiple of several commonly-used frame-rates: 24 frames per second (fps) for film, 30 fps for NTSC
(used for TV in North America and Japan), and 25 fps for PAL (used for TV in Europe). Using a timescale of 600,
you can exactly represent any number of frames in these systems.
In addition to a simple time value, a CMTime can represent non-numeric values: +infinity, -infinity, and indefinite.
It can also indicate whether the time been rounded at some point, and it maintains an epoch number.
Time and Media Representations
Representations of Time
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
55Using CMTime
You create a time using CMTimeMake, or one of the related functions such as CMTimeMakeWithSeconds
(which allows you to create a time using a float value and specify a preferred time scale). There are several
functions for time-based arithmetic and to compare times, as illustrated in the following example.
CMTime time1 = CMTimeMake(200, 2); // 200 half-seconds
CMTime time2 = CMTimeMake(400, 4); // 400 quarter-seconds
// time1 and time2 both represent 100 seconds, but using different timescales.
if (CMTimeCompare(time1, time2) == 0) {
NSLog(@"time1 and time2 are the same");
}
Float64 float64Seconds = 200.0 / 3;
CMTime time3 = CMTimeMakeWithSeconds(float64Seconds , 3); // 66.66... third-seconds
time3 = CMTimeMultiply(time3, 3);
// time3 now represents 200 seconds; next subtract time1 (100 seconds).
time3 = CMTimeSubtract(time3, time1);
CMTimeShow(time3);
if (CMTIME_COMPARE_INLINE(time2, ==, time3)) {
NSLog(@"time2 and time3 are the same");
}
For a list of all the available functions, see CMTime Reference .
Special Values of CMTime
Core Media provides constants for special values: kCMTimeZero, kCMTimeInvalid,
kCMTimePositiveInfinity, and kCMTimeNegativeInfinity. There are many ways, though in which a
CMTime can, for example, represent a time that is invalid. If you need to test whether a CMTime is valid, or a
non-numeric value, you should use an appropriate macro, such as CMTIME_IS_INVALID,
CMTIME_IS_POSITIVE_INFINITY, or CMTIME_IS_INDEFINITE.
CMTime myTime = <#Get a CMTime#>;
if (CMTIME_IS_INVALID(myTime)) {
Time and Media Representations
Representations of Time
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
56// Perhaps treat this as an error; display a suitable alert to the user.
}
You should not compare the value of an arbitrary CMTime with kCMTimeInvalid.
Representing a CMTime as an Object
If you need to use CMTimes in annotations or Core Foundation containers, you can convert a CMTime to and
from a CFDictionary (see CFDictionaryRef) using CMTimeCopyAsDictionary and
CMTimeMakeFromDictionary respectively. You can also get a string representation of a CMTime using
CMTimeCopyDescription.
Epochs
The epoch number of a CMTime is usually set to 0, but you can use it to distinguish unrelated timelines. For
example, the epoch could be incremented each cycle through a presentation loop, to differentiate between
time N in loop 0 from time N in loop 1.
CMTimeRange Represents a Time Range
CMTimeRange is a C structure that has a start time and duration, both expressed as CMTimes. A time range
does not include the time that is the start time plus the duration.
You create a time range using CMTimeRangeMake or CMTimeRangeFromTimeToTime. There are constraints
on the value of the CMTimes’ epochs:
● CMTimeRanges cannot span different epochs.
● The epoch in a CMTime that represents a timestamp may be non-zero, but you can only perform range
operations (such as CMTimeRangeGetUnion) on ranges whose start fields have the same epoch.
● The epoch in a CMTime that represents a duration should always be 0, and the value must be non-negative.
Working with Time Ranges
Core Media provides functions you can use to determine whether a time range contains a given time or other
time range, or whether two time ranges are equal, and to calculate unions and intersections of time ranges,
such as CMTimeRangeContainsTime, CMTimeRangeEqual, CMTimeRangeContainsTimeRange, and
CMTimeRangeGetUnion.
Given that a time range does not include the time that is the start time plus the duration, the following
expression always evaluates to false:
Time and Media Representations
Representations of Time
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
57CMTimeRangeContainsTime(range, CMTimeRangeGetEnd(range))
For a list of all the available functions, see CMTimeRange Reference .
Special Values of CMTimeRange
Core Media provides constants for a zero-length range and an invalid range, kCMTimeRangeZero and
kCMTimeRangeInvalid respectively. There are many ways, though in which a CMTimeRange can be invalid,
or zero—or indefinite (if one of the CMTimes is indefinite. If you need to test whether a CMTimeRange is valid,
zero, or indefinite, you should use an appropriate macro: CMTIMERANGE_IS_VALID,
CMTIMERANGE_IS_INVALID, CMTIMERANGE_IS_EMPTY, or CMTIMERANGE_IS_EMPTY.
CMTimeRange myTimeRange = <#Get a CMTimeRange#>;
if (CMTIMERANGE_IS_EMPTY(myTimeRange)) {
// The time range is zero.
}
You should not compare the value of an arbitrary CMTimeRange with kCMTimeRangeInvalid.
Representing a CMTimeRange as an Object
If you need to use CMTimeRangesin annotations or Core Foundation containers, you can convert a CMTimeRange
to and from a CFDictionary (see CFDictionaryRef) using CMTimeRangeCopyAsDictionary and
CMTimeRangeMakeFromDictionary respectively. You can also get a string representation of a CMTime using
CMTimeRangeCopyDescription.
Representations of Media
Video data and its associated metadata is represented in AV Foundation by opaque objects from the Core
Media framework. Core Media represents video data using CMSampleBuffer (see CMSampleBufferRef).
CMSampleBuffer is a Core Foundation-style opaque type; an instance contains the sample buffer for a frame
of video data as a Core Video pixel buffer (see CVPixelBufferRef). You access the pixel buffer from a sample
buffer using CMSampleBufferGetImageBuffer:
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(<#A CMSampleBuffer#>);
Time and Media Representations
Representations of Media
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
58From the pixel buffer, you can accessthe actual video data. For an example,see “Converting a CMSampleBuffer
to a UIImage” (page 59).
In addition to the video data, you can retrieve a number of other aspects of the video frame:
● Timing information You get accurate timestamps for both the original presentation time and the decode
timeusingCMSampleBufferGetPresentationTimeStampandCMSampleBufferGetDecodeTimeStamp
respectively.
● Format information The format information is encapsulated in a CMFormatDescription object (see
CMFormatDescriptionRef). From the format description, you can get for example the pixel type and
video dimensions using CMVideoFormatDescriptionGetCodecType and
CMVideoFormatDescriptionGetDimensions respectively.
● Metadata Metadata are stored in a dictionary as an attachment. You use CMGetAttachment to retrieve
the dictionary:
CMSampleBufferRef sampleBuffer = <#Get a sample buffer#>;
CFDictionaryRef metadataDictionary =
CMGetAttachment(sampleBuffer, CFSTR("MetadataDictionary", NULL);
if (metadataDictionary) {
// Do something with the metadata.
}
Converting a CMSampleBuffer to a UIImage
The following function shows how you can convert a CMSampleBuffer to a UIImage object. You should consider
your requirements carefully before using it. Performing the conversion is a comparatively expensive operation.
It is appropriate to, for example, create a still image from a frame of video data taken every second or so. You
should not use this as a means to manipulate every frame of video coming from a capture device in real time.
UIImage *imageFromSampleBuffer(CMSampleBufferRef sampleBuffer) {
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// Lock the base address of the pixel buffer.
CVPixelBufferLockBaseAddress(imageBuffer,0);
// Get the number of bytes per row for the pixel buffer.
Time and Media Representations
Converting a CMSampleBuffer to a UIImage
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
59size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
// Get the pixel buffer width and height.
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
// Create a device-dependent RGB color space.
static CGColorSpaceRef colorSpace = NULL;
if (colorSpace == NULL) {
colorSpace = CGColorSpaceCreateDeviceRGB();
if (colorSpace == NULL) {
// Handle the error appropriately.
return nil;
}
}
// Get the base address of the pixel buffer.
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
// Get the data size for contiguous planes of the pixel buffer.
size_t bufferSize = CVPixelBufferGetDataSize(imageBuffer);
// Create a Quartz direct-access data provider that uses data we supply.
CGDataProviderRef dataProvider =
CGDataProviderCreateWithData(NULL, baseAddress, bufferSize, NULL);
// Create a bitmap image from data supplied by the data provider.
CGImageRef cgImage =
CGImageCreate(width, height, 8, 32, bytesPerRow,
colorSpace, kCGImageAlphaNoneSkipFirst |
kCGBitmapByteOrder32Little,
dataProvider, NULL, true, kCGRenderingIntentDefault);
CGDataProviderRelease(dataProvider);
// Create and return an image object to represent the Quartz image.
UIImage *image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
Time and Media Representations
Converting a CMSampleBuffer to a UIImage
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
60CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return image;
}
Time and Media Representations
Converting a CMSampleBuffer to a UIImage
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
61This table describes the changes to AV Foundation Programming Guide .
Date Notes
2011-10-12 Updated for iOS5 to include references to release notes.
2011-04-28 First release for OS X v10.7.
2010-09-08 TBD
First version of a document that describes a low-level framework you use
to play, inspect, create, edit, capture, and transcode media assets.
2010-08-16
2011-10-12 | © 2011 Apple Inc. All Rights Reserved.
62
Document Revision HistoryApple Inc.
© 2011 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Cocoa, iPhone, iPod, iPod
touch, Mac, Objective-C, OS X, Quartz, and
QuickTime are trademarks of Apple Inc.,
registered in the U.S. and other countries.
OpenGL is a registered trademark of Silicon
Graphics, Inc.
Times is a registered trademark of Heidelberger
Druckmaschinen AG, available from Linotype
Library GmbH.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
Preferences and Settings
Programming GuideContents
About Preferences and Settings 5
At a Glance 5
You Decide What Preferences You Want to Expose 5
Apps Provide Their Own Preferences Interface 5
Apps Access Preferences Using the User Defaults Object 6
iCloud Stores Shared Preference and Configuration Data 6
Defaults Are Grouped into Domains in OS X 6
A Settings Bundle Manages Preferences for iOS Apps 6
See Also 7
About the User Defaults System 8
What Makes a Good Preference? 8
Providing a Preference Interface 8
The Organization of Preferences 9
The Argument Domain 10
The Application Domain 10
The Global Domain 11
The Languages Domains 11
The Registration Domain 11
Viewing Preferences Using the Defaults Tool 12
Accessing Preference Values 13
Registering Your App’s Default Preferences 13
Getting and Setting Preference Values 14
Synchronizing and Detecting Preference Changes 15
Managing Preferences Using Cocoa Bindings 16
Managing Preferences Using Core Foundation 16
Setting a Preference Value Using Core Foundation 16
Getting a Preference Value Using Core Foundation 17
Storing Preferences in iCloud 19
Strategies for Using the iCloud Key-Value Store 19
Configuring Your App to Use the Key-Value Store 20
Accessing Values in the Key-Value Store 21
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
2Defining the Scope of Key-Value Store Changes 22
Implementing an iOS Settings Bundle 24
The Settings App Interface 24
The Settings Bundle 26
The Settings Page File Format 27
Hierarchical Preferences 27
Localized Resources 28
Creating and Modifying the Settings Bundle 29
Adding the Settings Bundle 29
Preparing the Settings Page for Editing 29
Configuring a Settings Page: A Tutorial 31
Creating Additional Settings Page Files 33
Debugging Preferences for Simulated Apps 34
Document Revision History 35
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
3
ContentsFigures, Tables, and Listings
About the User Defaults System 8
Table 1-1 Options for displaying preferences to the user 8
Table 1-2 Search order for domains 10
Accessing Preference Values 13
Listing 2-1 Registering default preference values 14
Listing 2-2 Writing a simple default 17
Listing 2-3 Reading a simple default 17
Storing Preferences in iCloud 19
Listing 3-1 Updating local preference values using iCloud 21
Implementing an iOS Settings Bundle 24
Figure 4-1 Organizing preferences using child panes 28
Figure 4-2 Formatted contents of the Root.plist file 30
Figure 4-3 A root Settings page 31
Table 4-1 Preference control types 25
Table 4-2 Contents of the Settings.bundle directory 26
Table 4-3 Root-level keys of a preferences Settings page file 27
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
4Preferences are pieces of information that you store persistently and use to configure your app. Apps often
expose preferences to users so that they can customize the appearance and behavior of the app. Most
preferences are stored locally using the Cocoa preferences system—known as the user defaults system. Apps
can also store preferences in a user’s iCloud account using the key-value store.
The user defaultssystem and key-value store are both designed forstoring simple data types—strings, numbers,
dates, Boolean values, URLs, data objects, and so forth—in a property list. The use of a property list also means
you can organize your preference data using array and dictionary types. It is also possible to store other objects
in a property list by encoding them into an NSData object first.
At a Glance
Apps integrate preferences in several ways, including programmatically at various points throughout your
code and as part of the user interface. Preferences are supported in both iOS and Mac apps.
You Decide What Preferences You Want to Expose
Preferences are different for each app, and it is up to you to decide what parts of your app you want to make
configurable. Configuration involves checking the value of a stored preference from your code and taking
action based on that value. Thus, the preference value itself should always be simple and have a specific
meaning that is then implemented by your app.
Relevant section: “What Makes a Good Preference?” (page 8)
Apps Provide Their Own Preferences Interface
Because each app’s preferences are different, the app itself is responsible for deciding how best to present
those preferences to the user, if at all. Both iOS and OS X provide some standard places for you to incorporate
a preferences interface, but you are still responsible for designing that interface and displaying it at the
appropriate time.
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
5
About Preferences and SettingsRelevant section: “Providing a Preference Interface” (page 8)
Apps Access Preferences Using the User Defaults Object
Apps accesslocally stored preferences using a user defaults object, which is either an NSUserDefaults object
(iOS and OS X) or an NSUserDefaultsController object (OS X only). In addition to retrieving preference
values, apps can use this object to register default values for preferences and manage other aspects of the
preferences system.
Relevant chapter: “Accessing Preference Values” (page 13)
iCloud Stores Shared Preference and Configuration Data
Apps that support iCloud can put some of their preference data in the user’s iCloud account and make it
available to instances of the app running on the user’s other devices. You use this capability to supplement
(not replace) your app’s existing preferences data and provide a more coherent experience across the user’s
devices. For example, a magazine app might store information about the page number and issue last read by
the user so that the app running on a different device can show that same page.
Relevant chapter: “Storing Preferences in iCloud” (page 19)
Defaults Are Grouped into Domains in OS X
OS X preferences are grouped by domainsso thatsystem preferences can be differentiated from app preferences.
Splitting preferences in this manner lets the user specify some preferences globally and then override one or
more of those preferences inside an app.
Relevant section: “The Organization of Preferences” (page 9)
A Settings Bundle Manages Preferences for iOS Apps
An iOS, apps can display preferences from the Settings app, which is a good place to put preferences that the
user does not need to configure frequently. To display preferences in the Settings app, an app’s bundle must
include a special resource called a Settings bundle that defines the preferences to display, the proper way to
display them, and the information needed to record the user’s selections.
About Preferences and Settings
At a Glance
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
6Note: Apps are not required to use a Settings bundle to manage all preferences. For preferences
that the user islikely to change frequently, the app can display its own custom interface for managing
those preferences.
Relevant chapter: “Implementing an iOS Settings Bundle” (page 24)
See Also
For information about property lists, see Property List Programming Guide .
For more advanced information about using Core Foundation to manage preferences, see Preferences
Programming Topics for Core Foundation .
About Preferences and Settings
See Also
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
7The user defaults system manages the storage of preferences for each user. Most preferences are stored
persistently and therefore do not change between subsequent launch cycles of your app. Apps use preferences
to track user-initiated and program-initiated configuration changes.
What Makes a Good Preference?
When defining your app’s preferences, it is better to use simple values and data types whenever possible. The
preferences system is built around property-list data types such as strings, numbers, and dates. Although you
can use an NSData object to store arbitrary objects in preferences, doing so is not recommended in most
cases.
Storing objects persistently means that your app has to decode that object at some point. In the case of
preferences, a stored object means decoding the object every time you access the preference. It also means
that a newer version of your app has to ensure that it is able to decode objects created and written to disk
using an earlier version of your app, which is potentially error prone.
A better approach for preferences is to store simple strings and values and use them to create the objects your
app needs. Storing simple values meansthat your app can always accessthe value. The only thing that changes
from release to release is the interpretation of the simple value and the objects your app creates in response.
Providing a Preference Interface
For user-facing preferences, Table 1-1 lists the options for displaying those preferences to the user. As you can
see from this table, most options involve the creation of a custom user interface for managing and presenting
preferences. If you are creating an iOS app, you can use a Settings bundle to present preferences, but you
should do so only for settings the user changes infrequently.
Table 1-1 Options for displaying preferences to the user
Preference iOS OS X
Frequently changed preferences Custom UI Custom UI
Infrequently changed preferences Settings bundle Custom UI
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
8
About the User Defaults SystemNote: An example of preferencesthat might change frequently include thingslike the volume levels
or control options of a game. An example of preferences that might change infrequently are the
email address and server settings in the Mail app. For iOS apps, it is ultimately up to you to decide
whether it is appropriate to expose preferences from the Settings app or from inside your app.
Preferences in Mac apps should be accessible from a Preferences menu item in the app menu. Cocoa apps
created using the Xcode templates provide such a menu item for you automatically. It is your responsibility to
present an appropriate user interface when the user choosesthis menu item. You can provide that user interface
by defining an action method in your app delegate that displays a custom preferences window and connecting
that action method to the menu item in Interface Builder.
There is no standard way to display custom preferences from inside an iOS app. You can integrate preferences
in many ways, including using a separate tab in a tab-bar interface or using a custom button from one of your
app’s screens. Preferences should generally be presented using a distinct view controller so that changes in
preferences can be recorded when that view controller is dismissed by the user.
The Organization of Preferences
Preferences are grouped into domains, each of which has a name and a specific usage. For example, there’s a
domain for app-specific preferences and another for systemwide preferences that apply to all apps. All
preferences are stored and accessed on a per-user basis. There is no support for sharing preferences between
users.
Each preference has three components:
● The domain in which it is stored
●
Its name (specified as an NSString object)
●
Its value, which can be any property-list object (NSData, NSString, NSNumber, NSDate, NSArray, or
NSDictionary)
The lifetime of a preference depends on which domain you store it in. Some domains store preferences
persistently by writing them to the user’s defaults database. Such preferences continue to exist from one app
launch to the next. Other domains store preferences in a more volatile way, preserving preference values only
for the life of the corresponding user defaults object.
About the User Defaults System
The Organization of Preferences
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
9A search for the value of a given preference proceeds through the domains in an NSUserDefaults object’s
search list. Only domains in the search list are searched and they are searched in the order shown in Table 1-2,
starting with the NSArgumentDomain domain. A search ends when a preference with the specified name is
found. If multiple domains contain the same preference, the value is taken from the domain nearest the
beginning of the search list.
Table 1-2 Search order for domains
Domain State
NSArgumentDomain volatile
Application (Identified by the app’s identifier) persistent
NSGlobalDomain persistent
Languages (Identified by the language names) volatile
NSRegistrationDomain volatile
The Argument Domain
The argument domain comprises values set from command- line arguments (if you started the app from the
command line) and is identified by the NSArgumentDomain constant. Values set from the command line are
automatically placed into this domain by the system. To add a value to this domain, specify the preference
name on the command line (preceded with a hyphen) and follow it with the corresponding value. For example,
the following command launches Xcode and sets the value of its IndexOnOpen preference to NO:
localhost> Xcode.app/Contents/MacOS/Xcode -IndexOnOpen NO
Preferencesset from the command line temporarily override the established valuesstored in the user’s defaults
database. In the preceding example,setting the IndexOnOpen preference to NO prevents Xcode from indexing
projects automatically, even if the preference is set to YES in the user defaults database.
The Application Domain
The application domain contains app-specific preferences that are stored in the user defaults database of the
current user. When you use the shared NSUserDefaults object (or a NSUserDefaultsController object
in OS X) to write preferences, those preferences are automatically placed in this domain.
About the User Defaults System
The Organization of Preferences
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
10Because this domain is app-specific, the contents of the domain are tied to your app’s bundle identifier. The
contents of this domain are stored in a file that is managed by the system. Currently, this file is located in the
$HOME/Library/Preferences/ directory, where $HOME is either the app’s home directory or the user’s
home directory (depending on the platform and whether your app is in a sandbox). The name of the user
defaults database file is .plist, where is your
app’s bundle identifier. You should not modify this file directly but can inspect it during debugging to make
sure preference values are being written by your app.
The Global Domain
The global domain contains preferencesthat are applicable to all apps and isidentified by the NSGlobalDomain
constant. This domain is typically used by system frameworks to store system-wide values and should not be
used by your app to store app-specific values. If you want to change the value of a preference in the global
domain, write that same preference to the application domain with the new value.
Examples of how the system frameworks use this domain:
●
Instances of the NSRuleView class store the user’s preferred measurement units in the
AppleMeasurementUnits key. Using this storage location causes ruler views in all apps to use the same
units.
● The system uses the AppleLanguages key to store the user’s preferred languages as an array of strings.
For example, a user could specify English as the preferred language, followed by Spanish, French, German,
Italian, and Swedish.
The Languages Domains
For each language in the AppleLanguages preference, the system recordslanguage-specific preference values
in a domain whose name is based on the language name. Each language-specific domain contains preferences
for the corresponding locale. Many classes in the Foundation framework (such as the NSDate,
NSDateFormatter, NSTimeZone, NSString, and NSScanner classes) use this locale information to modify
their behavior. For example, when you request a string representation of an NSCalendarDate object, the
NSCalendarDate object uses the locale information to find the names of months and the days of the week
for the user’s preferred language.
The Registration Domain
The registration domain defines the set of default values to use if a given preference is not set explicitly in one
of the other domains. At launch time, an app can call the registerDefaults: method of NSUserDefaults
to specify a default set of values for important preferences. When an app launches for the first time, most
preferences have no values,so retrieving them would yield undefined results. Registering a set of default values
ensures that your app always has a known good set of values to operate on.
About the User Defaults System
The Organization of Preferences
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
11The contents of the registration domain can be set only by using the registerDefaults: method.
Viewing Preferences Using the Defaults Tool
In OS X, the defaults command-line tool provides a way for you to examine the contents of the user defaults
database. During app development, you might use this tool to validate the preferences your app is writing to
disk. To do that, you would use a command of the following form from the Terminal app:
defaults read
To read the contents of the global domain, you would use the following command:
defaults read NSGlobalDomain
For more information about using the defaults tool to read and write preference values, see defaults man
page.
About the User Defaults System
Viewing Preferences Using the Defaults Tool
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
12You use the NSUserDefaults class to gain access to your app’s preferences. Each app is provided with a
single instance of this class, accessible from the standardUserDefaults class method. You use the shared
user defaults object to:
● Specify any default values for your app’s preferences at launch time.
● Get and set individual preference values stored in the app domain.
● Remove preference values.
● Examine the contents of the volatile preference domains.
Mac appsthat use Cocoa bindings can use an NSUserDefaultsController object to set and get preferences
automatically. You typically add such an object to the same nib file you use for displaying user-facing preferences.
You bind your user interface controls to items in the user defaults controller, which handles the process of
getting and setting values in the user defaults database.
Preference values must be one of the standard property list object types: NSData, NSString, NSNumber,
NSDate, NSArray, or NSDictionary. The NSUserDefaults class also provides built-in manipulations for
storing NSURL objects as preference values. For more information about property lists and their contents, see
Property List Programming Guide .
Registering Your App’s Default Preferences
At launch time, an app should register default values for any preferences that it expects to be present and
valid. When you request the value of a preference that has never been set, the methods of the NSUserDefaults
class return default values that are appropriate for the data type. For numerical scalar values, this typically
means returning 0, but for strings and other objects it means returning nil. If these standard default values
are not appropriate for your app, you can register your own default values using the registerDefaults:
method. This method places your custom default values in the NSRegistrationDomain domain, which
causes them to be returned when a preference is not explicitly set.
When calling the registerDefaults: method, you must provide a dictionary of all the default values you
need to register. Listing 2-1 shows an example where an iOS app registers its default values early in the launch
cycle. You can register default values at any time, of course, butshould alwaysregister them before attempting
to retrieve any preference values.
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
13
Accessing Preference ValuesListing 2-1 Registering default preference values
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Register the preference defaults early.
NSDictionary *appDefaults = [NSDictionary
dictionaryWithObject:[NSNumber numberWithBool:YES]
forKey:@"CacheDataAgressively"];
[[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
// Other initialization...
}
When registering default values for scalar types, use an NSNumber object to specify the value for the number.
If you want to register a preference whose value is a URL, use the archivedDataWithRootObject: method
of NSKeyedArchiver to encode the URL in an NSData object first. Although you can use a similar technique
for other types of objects, you should avoid doing so when a simpler option is available.
Getting and Setting Preference Values
You get and set preference values using the methods of the NSUserDefaults class. This class has methods
for getting and setting preferences with scalar values of type Boolean, integer, float, and double. It also
has methodsfor getting and setting preferences whose value is an object of type NSData, NSDate, NSString,
NSNumber, NSArray, NSDictionary, and NSURL. There are two situations where you might get preference
values and one where you might set them:
● Get preference values:
● When you need to use the value to configure your app’s behavior.
● When you need to display the value in your preferences interface.
● Set preference values when the user changes them in your preferences interface.
The following code shows how you might get a preference value in your code. In this example, the code
retrieves the value of the CacheDataAggressively key, which is custom key that the app might use to
determine its caching strategy. Code like this can be used anywhere to handle custom configuration of your
app. If you wanted to display this particular preference value to the user, you would use similar code to configure
the controls of your preferences interface.
Accessing Preference Values
Getting and Setting Preference Values
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
14if ([[NSUserDefaults standardUserDefaults] boolForKey:@"CacheDataAggressively"])
{
// Delete the backup file.
}
To set a preference value programmatically, you call the corresponding setter methods of NSUserDefaults.
When setting object values, you must use the setObject:forKey: method. When calling this method, you
must make sure that the object is one of the standard property list types. The following example sets some
preferences based on the state of the app’s preferences interface.
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if ([cacheAgressivelyButton state] == NSOnState) {
// The user wants to cache files aggressively.
[defaults setBool:YES forKey:@"CacheDataAggressively"];
[defaults setObject:[NSDate dateWithTimeIntervalSinceNow:(3600 * 24 * 7)]
forKey:@"CacheExpirationDate"]; // Set a 1-week expiration
} else {
// The user wants to use lazy caching.
[defaults setBool:NO forKey:@"CacheDataAggressively"];
[defaults removeObjectForKey:@"CacheExpirationDate"];
}
You do not have to display a preferences interface to manage all values. Your app can use preferences to cache
interesting information. For example, NSWindow objectsstore their current location in the user defaultssystem.
This data allows them to return to the same location the next time the user starts the app.
Synchronizing and Detecting Preference Changes
Because the NSUserDefaults class caches values, it issometimes necessary to synchronize the cached values
with the current contents of the user defaults database. Your app is not always the only entity modifying the
user defaults database. In iOS, the Settings app can modify the values of preferences for apps that have a
Settings bundle. In OS X, the system and other apps might modify preferences values in response to user
actions. For example, if the user changes preferred languages, the system writes the new values to the user
defaults database. In OS X v10.5 and later, the shared NSUserDefaults object synchronizes its caches
automatically at periodic intervals. However, apps can call the synchronize method manually to force an
update of the cached values.
Accessing Preference Values
Synchronizing and Detecting Preference Changes
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
15To detect when changes to a preference value occur, apps can also register for the notification
NSUserDefaultsDidChangeNotification. The shared NSUserDefaults object sends this notification
to your app whenever it detects a change to a preference located in one of the persistent domains. You can
use this notification to respond to changes that might impact your user interface. For example, you could use
it to detect changes to the user’s preferred language and update your app content appropriately.
Managing Preferences Using Cocoa Bindings
Mac apps can use Cocoa bindings to set preference values directly from their user interfaces. Modifying
preferences using bindings involves adding an NSUserDefaultsController object to the appropriate nib
files and binding the values of your controls to the preference values in the user defaults database. When your
app showsthe interface, the user defaults controller automatically loads valuesfrom the user defaults database
and uses them to set the value of controls. Similarly, when the user changes the value in a control, the user
defaults controller updates the value in the user defaults database.
For more information on how to use the NSUserDefaultsController class to bind preference values to
your user interface, see “User Defaults and Bindings” in Cocoa Bindings Programming Topics.
Managing Preferences Using Core Foundation
The Core Foundation framework provides its own set of interfaces for accessing preferences stored in the user
defaults database. Like the NSUserDefaults class, you can use Core Foundation functions to get and set
preference values and synchronize the user defaults database. Unlike NSUserDefaults, you can use the Core
Foundation functions to write preferences for different apps and on different computers. Note that modifying
some preferences domains(those not belonging to the current app and user) requiresroot privileges(or admin
privileges prior to OS X v10.6); for information on how to gain suitable privileges, see Authorization Services
Programming Guide . Writing outside the app domain is not possible for apps installed in a sandbox.
For information about the Core Foundation functions for getting and setting preferences, see Preferences
Utilities Reference .
Setting a Preference Value Using Core Foundation
Preferences are stored as key-value pairs. The key must be a CFString object, but the value can be any Core
Foundation property list value (see Property List Programming Topics for Core Foundation ), including the
container types. For example, you might have a key called defaultWindowWidth that defines the width in
Accessing Preference Values
Managing Preferences Using Cocoa Bindings
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
16pixels of any new windows that your app creates. Its value would most likely be of type CFNumber. You might
also decide to combine window width and height into a single preference called defaultWindowSize and
make its value be a CFArray object containing two CFNumber objects.
The code in Listing 2-2 demonstrates how to create a simple preference for the app MyTextEditor. The example
sets the default text color for the app to blue.
Listing 2-2 Writing a simple default
CFStringRef textColorKey = CFSTR("defaultTextColor");
CFStringRef colorBLUE = CFSTR("BLUE");
// Set up the preference.
CFPreferencesSetAppValue(textColorKey, colorBLUE,
kCFPreferencesCurrentApplication);
// Write out the preference data.
CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
Notice that CFPreferencesSetAppValue by itself is not sufficient to create the new preference. A call to
CFPreferencesAppSynchronize isrequired to actually save the value. If you are writing multiple preferences,
it is more efficient to sync only once after the last value has been set than to sync after each individual value
is set. For example, if you implement a preference pane you might synchronize only when the user presses an
OK button. In other cases you might not want to sync at all until the app quits—although note that if the app
crashes, all unsaved preferences settings will be lost.
Getting a Preference Value Using Core Foundation
The simplest way to locate and retrieve a preference value is to use the CFPreferencesCopyAppValue
function. This call searches through the various preference domains in order until it finds the key you have
specified. If a preference has been set in a less specific domain—Any Application, for example —its value is
retrieved with this call if a more specific version cannot be found. Listing 2-3 shows how to retrieve the text
color preference saved in Listing 2-2 (page 17).
Listing 2-3 Reading a simple default
CFStringRef textColorKey = CFSTR("defaultTextColor");
CFStringRef textColor;
Accessing Preference Values
Managing Preferences Using Core Foundation
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
17// Read the preference.
textColor = (CFStringRef)CFPreferencesCopyAppValue(textColorKey,
kCFPreferencesCurrentApplication);
// When finished with value, you must release it
// CFRelease(textColor);
All values returned from preferences are immutable, even if you have just set the value using a mutable object.
Accessing Preference Values
Managing Preferences Using Core Foundation
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
18An app can use the iCloud key-value store to share small amounts of data with other instances of itself on the
user’s other computers and iOS devices. The key-value store is intended for simple data types like those you
might use for preferences. For example, a magazine app might store the current issue and page number being
read by the user so that other instances of the app can open to the same page when launched. You should
not use this store for large amounts of data or for complex data types.
To use the iCloud key-value store, do the following:
1. In Xcode, configure the com.apple.developer.ubiquity-kvstore-identifier entitlement for
your app.
2. In your code, create the shared NSUbiquitousKeyValueStore object and register for change notifications.
3. Use the methods of NSUbiquitousKeyValueStore to get and set values.
Key-value data in iCloud is limited to simple property-list types (strings, numbers, dates, and so on).
Strategies for Using the iCloud Key-Value Store
The key-value store is not intended for storing large amounts of data. It is intended for storing configuration
data, preferences, and small amounts of app-related data. To help you decide whether the key-value store is
appropriate for your needs, consider the following:
● Each app is limited to 1 MB of total space in the key-value store. (There is also a separate per-key limit of
1 MB and a maximum of 1024 keys are allowed.) Thus, you cannot use the key-value store to share large
amounts of data.
● The key-value store supports only property-list types. Property-list types include simple types such as
NSNumber, NSString, and NSDate objects. You can also store raw blocks of data in NSData objects and
arrange all of the types using NSArray and NSDictionary objects.
● The key-value store is intended for storing data that changes infrequently. If the apps on a device make
frequent changes to the key-value store, the system may defer the synchronization of some changes in
order to minimize the number of round trips to the server. The more frequently apps make changes, the
more likely it is that later changes will be deferred and not show up on other devices right away.
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
19
Storing Preferences in iCloud● The key-value store is not a replacement for preferences or other local techniques for saving the same
data. The purpose of the key-value store is to share data between apps, but if iCloud is not enabled or is
not available on a given device, you still might want to keep a local copy of the data.
If you are using the key-value store to share preferences, one approach is to store the actual values in the user
defaults database and synchronize them using the key-value store. (If you do not want to use the preferences
system, you could also save the changes in a custom property-list file or some other local storage.) When you
change the value of a key locally, write that change to both the user defaults database and to the iCloud
key-value store at the same time. To receive changesfrom externalsources, add an observer for the notification
NSUbiquitousKeyValueStoreDidChangeExternallyNotification and use your handler method to
detect which keys changed externally and update the corresponding data in the user defaults database. By
doing this, your user defaults database always contains the correct configuration values. The iCloud key-value
store simply becomes a mechanism for ensuring that the user defaults database has the most recent changes.
Configuring Your App to Use the Key-Value Store
In order to use of the key-value store, an app must be explicitly configured with the
com.apple.developer.ubiquity-kvstore-identifier entitlement. You use Xcode to enable this
entitlement and specify its value for your app:
1. In your Xcode project, select the target for your app.
2. In the Summary tab, enable the Entitlements option.
3. Specify a value for the iCloud Key-Value Store field.
When you enable entitlements, Xcode automatically fills in a default value for the iCloud Key-Value Store field
that is based on the bundle identifier of your app. For most apps, the default value is what you want. However,
if your app shares its key-value storage with another app, you must specify the bundle identifier for the other
app instead. For example, if you have a lite version of your app, you might want it to use the same key-value
store as the paid version.
Enabling the entitlement is all you have to do to use the shared NSUbiquitousKeyValueStore object. As
long as the entitlement is configured and contains a valid value, the key-value store object writes its data to
the appropriate location in the user’s iCloud account. If there is a problem attaching to the specified iCloud
container, any attemptsto read or write key values will fail. To ensure the key-value store is configured properly
and accessible, you should execute code similar to the following early in your app’s launch cycle:
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[[NSNotificationCenter defaultCenter] addObserver:self
Storing Preferences in iCloud
Configuring Your App to Use the Key-Value Store
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
20selector:@selector(updateKVStoreItems:)
name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:store];
[store synchronize];
Creating the key-value store object early in your app’s launch cycle is recommended because it ensures that
your app receives updates from iCloud in a timely manner. The best way to determine if changes have been
made to keys and values is to register for the notification
NSUbiquitousKeyValueStoreDidChangeExternallyNotification. And at launch time, you should
call the synchronize method manually to detect if any changes were made externally. You do not need to
call that method at other times during you app’s execution.
For more information about how to configure entitlements for an iOS app, see “Configuring Apps” in Tools
Workflow Guide for iOS .
Accessing Values in the Key-Value Store
You get and set key-value store values using the methods of the NSUbiquitousKeyValueStore class. This
class has methods for getting and setting preferences with scalar values of type Boolean, long long, and
double. It also has methods for getting and setting keys whose values are NSData, NSDate, NSString,
NSNumber, NSArray, or NSDictionary objects.
If you are using the key-value store as a way to update locally stored preferences, you could use code similar
to that in Listing 3-1 to coordinate updates to the user defaults database. This example assumes that you use
the same key names and corresponding values in both iCloud and the user defaults database. It also assumes
that you previously registered the updateKVStoreItems: method as the method to call in response to the
notification NSUbiquitousKeyValueStoreDidChangeExternallyNotification.
Listing 3-1 Updating local preference values using iCloud
- (void)updateKVStoreItems:(NSNotification*)notification {
// Get the list of keys that changed.
NSDictionary* userInfo = [notification userInfo];
NSNumber* reasonForChange = [userInfo
objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
NSInteger reason = -1;
// If a reason could not be determined, do not update anything.
Storing Preferences in iCloud
Accessing Values in the Key-Value Store
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
21if (!reasonForChange)
return;
// Update only for changes from the server.
reason = [reasonForChange integerValue];
if ((reason == NSUbiquitousKeyValueStoreServerChange) ||
(reason == NSUbiquitousKeyValueStoreInitialSyncChange)) {
// If something is changing externally, get the changes
// and update the corresponding keys locally.
NSArray* changedKeys = [userInfo
objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
// This loop assumes you are using the same key names in both
// the user defaults database and the iCloud key-value store
for (NSString* key in changedKeys) {
id value = [store objectForKey:key];
[userDefaults setObject:value forKey:key];
}
}
}
Defining the Scope of Key-Value Store Changes
Every call to one of the NSUbiquitousKeyValueStore methods is treated as a single atomic transaction.
When transferring the data for that transaction to iCloud, the whole transaction either fails or succeeds. If it
succeeds, all of the keys are written to the store and if it fails no keys are written. There is no partial writing of
keys to the store. When a failure occurs, the system also generates a
NSUbiquitousKeyValueStoreDidChangeExternallyNotification notification that containsthe reason
for the failure. If you are using the key-value store, you should use that notification to detect possible problems.
Storing Preferences in iCloud
Defining the Scope of Key-Value Store Changes
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
22If you have a group of keys whose values must all be updated at the same time in order to be valid, save them
together in a single transaction. To write multiple keys and values in a single transaction, create an
NSDictionary object with all of the keys and values. Then write the dictionary object to the key-value store
using the setDictionary:forKey: method. Writing an entire dictionary of changes ensures that all of the
keys are written or none of them are.
Storing Preferences in iCloud
Defining the Scope of Key-Value Store Changes
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
23In iOS, the Foundation framework provides the low-level mechanism for storing the preference data. Apps
then have two options for presenting preferences:
● Display preferences inside the app.
● Use a Settings bundle to manage preferences from the Settings app.
Which option you choose depends on how you expect users to interact with preferences. The Settings bundle
is generally the preferred mechanism for displaying preferences. However, games and other apps that contain
configuration options or other frequently accessed preferences might want to present them inside the app
instead. Regardless of how you present them, you use the NSUserDefaults class to access preference values
from your code.
This chapter focuses on the creation of a Settings bundle for your app. A Settings bundle contains files that
describe the structure and presentation style of your preferences. The Settings app uses this information to
create an entry for your app and to display your custom preference pages.
For guidelines on how to manage and present settings and configuration options, see iOS Human Interface
Guidelines.
The Settings App Interface
The Settings app implements a hierarchical set of pages for navigating app preferences. The main page of the
Settings app liststhe system and third-party apps whose preferences can be customized. Selecting a third-party
app takes the user to the preferences for that app.
Every app with a Settings bundle has at least one page of preferences, referred to as the main page . If your
app has only a few preferences, the main page may be the only one you need. If the number of preferences
gets too large to fit on the main page, however, you can create child pages that link off the main page or other
child pages. There is no specific limit to the number of child pages you can create, but you should strive to
keep your preferences as simple and easy to navigate as possible.
The contents of each page consists of one or more controls that you configure. Table 4-1 lists the types of
controls supported by the Settings app and describes how you might use each type. The table also lists the
raw key name stored in the configuration files of your Settings bundle.
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
24
Implementing an iOS Settings BundleTable 4-1 Preference control types
Controltype Description
The text field type displays a title (optional) and an editable text field. You can use this
type for preferences that require the user to specify a custom string value.
The key for this type is PSTextFieldSpecifier.
Text field
The title type displays a read-only string value. You can use thistype to display read-only
preference values. (If the preference contains cryptic or nonintuitive values, this type
lets you map the possible values to custom strings.)
The key for this type is PSTitleValueSpecifier.
Title
The toggle switch type displays an ON/OFF toggle button. You can use this type to
configure a preference that can have only one of two values. Although you typically
use this type to represent preferences containing Boolean values, you can also use it
with preferences containing non-Boolean values.
The key for this type is PSToggleSwitchSpecifier.
Toggle
switch
The slider type displays a slider control. You can use this type for a preference that
represents a range of values. The value for this type is a real number whose minimum
and maximum value you specify.
The key for this type is PSSliderSpecifier.
Slider
The multivalue type lets the user select one value from a list of values. You can use this
type for a preference that supports a set of mutually exclusive values. The values can
be of any type.
The key for this type is PSMultiValueSpecifier.
Multivalue
The group type is for organizing groups of preferences on a single page. The group
type does not represent a configurable preference. It simply contains a title string that
is displayed immediately before one or more configurable preferences.
The key for this type is PSGroupSpecifier.
Group
The child pane type lets the user navigate to a new page of preferences. You use this
type to implement hierarchical preferences. For more information on how you configure
and use this preference type, see “Hierarchical Preferences” (page 27).
The key for this type is PSChildPaneSpecifier.
Child pane
For detailed information about the format of each preference type, see Settings Application Schema Reference .
To learn how to create and edit Settings page files, see “Creating and Modifying the Settings Bundle” (page
29).
Implementing an iOS Settings Bundle
The Settings App Interface
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
25The Settings Bundle
A Settings bundle hasthe name Settings.bundle and residesin the top-level directory of your app’s bundle.
This bundle contains one or more Settings page files that describe the individual pages of preferences. It may
also include other support files needed to display your preferences, such as images or localized strings. Table
4-2 lists the contents of a typical Settings bundle.
Table 4-2 Contents of the Settings.bundle directory
Item name Description
The Settings page file containing the preferences for the root page. The
name of thisfile must be Root.plist. The contents of thisfile are described
in more detail in “The Settings Page File Format” (page 27).
Root.plist
If you build a set of hierarchical preferences using child panes, the contents
for each child pane are stored in a separate Settings page file. You are
responsible for naming these files and associating them with the correct
child pane.
Additional .plist files
These directories store localized string resources for your Settings page files.
Each directory contains a single strings file, whose title is specified in your
Settings page file. The strings files provide the localized strings to display
for your preferences.
One or more .lproj
directories
If you use the slider control, you can store the images for your slider in the
top-level directory of the bundle.
Additional images
In addition to the Settings bundle, the app bundle can contain a custom icon for your app settings. The Settings
app displays the icon you provide next to the entry for your app preferences. For information about app icons
and how you specify them, see iOS App Programming Guide .
When the Settings app launches, it checks each custom app for the presence of a Settings bundle. For each
custom bundle it finds, it loadsthat bundle and displaysthe corresponding app’s name and icon in the Settings
main page. When the user taps the row belonging to your app, Settings loads the Root.plist Settings page
file for your Settings bundle and uses that file to build your app’s main page of preferences.
In addition to loading your bundle’s Root.plist Settings page file, the Settings app also loads any
language-specific resources for that file, as needed. Each Settings page file can have an associated .strings
file containing localized values for any user-visible strings. As it prepares your preferences for display, the
Settings app looksforstring resourcesin the user’s preferred language and substitutesthem in your preferences
page prior to display.
Implementing an iOS Settings Bundle
The Settings Bundle
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
26The Settings Page File Format
Each Settings page file is stored in the iPhone Settings property-list file format, which is a structured file format.
The simplest way to edit Settings page files is to use the built-in editor facilities of Xcode; see “Preparing the
Settings Page for Editing” (page 29). You can also edit property-list files using the Property List Editor app that
comes with the Xcode tools.
Note: Xcode converts any XML-based property files in your project to binary format when building
your app. This conversion saves space and is done for you automatically.
The root element of each Settings page file contains the keys listed in Table 4-3. Only one key is actually
required, but it is recommended that you include both of them.
Table 4-3 Root-level keys of a preferences Settings page file
Key Type Value
The value for this key is an array of dictionaries, with each
dictionary containing the information for a single control.
For a list of control types, see Table 4-1 (page 25). For a
description of the keys associated with each control, see
Settings Application Schema Reference .
PreferenceSpecifiers Array
(required)
The name of the strings file associated with this file. A copy
of this file (with appropriate localized strings) should be
located in each of your bundle’s language-specific project
directories. If you do not include this key, the strings in this
file are not localized. For information on how these strings
are used, see “Localized Resources” (page 28).
StringsTable String
Hierarchical Preferences
If you plan to organize your preferences hierarchically, each page you define must have its own separate
.plist file. Each .plist file contains the set of preferences displayed only on that page. Your app’s main
preferences page is always stored in a file called Root.plist. Additional pages can be given any name you
like.
To specify a link between a parent page and a child page, you include a child pane control in the parent page.
A child pane control creates a row that, when tapped, displays a new page of settings. The File key of the
child pane control identifies the name of the .plist file with the contents of the child page. The Title key
Implementing an iOS Settings Bundle
The Settings Bundle
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
27identifies the title of the child page; this title is also used as the text of the control used to display the child
page. The Settings app automatically provides navigation controls on the child page to allow the user to
navigate back to the parent page.
Figure 4-1 shows how this hierarchical set of pages works. The left side of the figure shows the .plist files,
and the right side shows the relationships between the corresponding pages.
Figure 4-1 Organizing preferences using child panes
Sounds
New Voicemail
Group 1
Group 2
New Email
Sent Mail
Ringtones
Sounds page
Settings
Group 1
Usage
Sounds
Group 2
Group 3
Brightness
Wallpaper
General
Root page
Sounds.plist
Root.plist
General.plist
General page
General
Date & Time
Group 1
Network
Keyboard
For more information about child pane controls and their associated keys, see Settings Application Schema
Reference .
Localized Resources
Because preferences contain user-visible strings, you should provide localized versions of those strings with
your Settings bundle. Each page of preferences can have an associated .strings file for each localization
supported by your bundle. When the Settings app encounters a key that supports localization, it checks the
appropriately localized .strings file for a matching key. If it finds one, it displays the value associated with
that key.
When looking for localized resources such as .strings files, the Settings app follows the same rules that
other iOS apps follow. It first tries to find a localized version of the resource that matches the user’s preferred
language setting. If no such resource exists, an appropriate fallback language is selected.
Implementing an iOS Settings Bundle
The Settings Bundle
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
28For information about the format ofstringsfiles, language-specific project directories, and how language-specific
resources are retrieved from bundles, see Internationalization Programming Topics.
Creating and Modifying the Settings Bundle
Xcode provides a template for adding a Settings bundle to your current project. The default Settings bundle
contains a Root.plist file and a default language directory for storing any localized resources. You can
expand this bundle as needed to include additional property list files and resources needed by your Settings
bundle.
Adding the Settings Bundle
To add a Settings bundle to your Xcode project:
1. Choose File > New > New File.
2. Under iOS, choose Resource, and then select the Settings Bundle template.
3. Name the file Settings.bundle.
In addition to adding a new Settings bundle to your project, Xcode automatically addsthat bundle to the Copy
Bundle Resources build phase of your app target. Thus, all you have to do is modify the property list files of
your Settings bundle and add any needed resources.
The new Settings bundle has the following structure:
Settings.bundle/
Root.plist
en.lproj/
Root.strings
Preparing the Settings Page for Editing
Before editing any of the property-list files in your Settings bundle, you should configure the Xcode editor to
format the contents of those files as iPhone settings. Xcode does this automatically for the Root.plist file,
but you may need to format additional property-list files manually. To format a file as iPhone Settings, do the
following:
1. Select the file.
2. Control-click the editor window and choose Property List Type > iPhone Settings plist if it is not already
chosen.
Implementing an iOS Settings Bundle
Creating and Modifying the Settings Bundle
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
29Formatting a property list makes it easier to understand and edit the file’s contents. Xcode substitutes
human-readable strings (as shown in Figure 4-2) that are appropriate for the selected format.
Figure 4-2 Formatted contents of the Root.plist file
Implementing an iOS Settings Bundle
Creating and Modifying the Settings Bundle
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
30Configuring a Settings Page: A Tutorial
This section shows you how to configure a Settings page to display the controls you want. The goal of the
tutorial is to create a page like the one in Figure 4-3. If you have not yet created a Settings bundle for your
project, you should do so as described in “Adding the Settings Bundle” (page 29) before proceeding with
these steps.
Figure 4-3 A root Settings page
1. Disclose the Preference Items key to display the default items that come with the template.
2. Change the title of Item 0 to Sound.
● Disclose Item 0 of Preference Items.
● Change the value of the Title key from Group to Sound.
● Leave the Type key set to Group.
● Click the disclosure triangle of the item to hide its contents.
3. Create the first toggle switch for the renamed Sound group.
● Select Item 2 (the toggle switch item) of Preference Items and choose Edit > Cut.
● Select Item 0 and choose Edit > Paste. (This moves the toggle switch item in front of the text field
item.)
● Disclose the toggle switch item to reveal its configuration keys.
● Change the value of the Title key to Play Sounds.
● Change the value of the Identifier key to play_sounds_preference.
Implementing an iOS Settings Bundle
Creating and Modifying the Settings Bundle
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
31● Click the disclosure triangle of the item to hide its contents.
4. Create a second toggle switch for the Sound group.
● Select Item 1 (the Play Sounds toggle switch).
● Choose Edit > Copy.
● Choose Edit >Paste to place a copy of the toggle switch right after the first one.
● Disclose the new toggle switch item to reveal its configuration keys.
● Change the value of its Title key to 3D Sound.
● Change the value of its Identifier key to 3D_sound_preference.
● Click the disclosure triangle of the item to hide its contents.
At this point, you have finished the first group of settings and are ready to create the User Info group.
5. Change Item 3 into a Group control and name it User Info.
● Click Item 3 in the Preferences Items. This displays a pop-up menu with a list of item types.
● From the pop-up menu, choose Group to change the type of the control.
● Disclose the contents of Item 3.
● Set the value of the Title key to User Info.
● Click the disclosure triangle of the item to hide its contents.
6. Create the Name field.
● Select Item 4 in the Preferences Items.
● Using the pop-up menu, change its type to Text Field.
● Set the value of the Title key to Name.
● Set the value of the Identifier key to user_name.
● Click the disclosure triangle of the item to hide its contents.
7. Create the Experience Level settings.
● Select Item 4.
● Control-click the editor window and select Add Row to add a new item.
● Set the type of the new item to Multi Value.
● Disclose the item’s contents and set its title to Experience Level, its identifier to
experience_preference, and its default value to 0.
● With the Default Value key selected, Control-click and select Add Row to add a Titles array.
● Select the Titles array and press Return to add a new subitem.
● Add two more subitems to create a total of three items.
Implementing an iOS Settings Bundle
Creating and Modifying the Settings Bundle
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
32● Set the values of the subitems to Beginner, Expert, and Master.
● Hide the key’s subitems.
● Add a new item for the Values array.
● Add three subitems to the Values array and set their values to 0, 1, and 2.
● Hide the contents of Item 5.
8. Add the final group to your settings page.
● Create a new item and set its type to Group and its title to Gravity.
● Create another new item and set itstype to Slider, itsidentifier to gravity_preference, its default
value to 1, and its maximum value to 2.
Creating Additional Settings Page Files
The Settings Bundle template includes the Root.plist file, which defines your app’s top Settings page. To
define additional Settings pages, you must add additional property list files to your Settings bundle.
To add a property list file to your Settings bundle in Xcode, do the following:
1. Choose File > New > New File.
2. Under iOS, select Resource, and then select the Property List template.
3. Select the new file to display its contents in the editor.
4. Control-click the editor pane and choose Property List Type > iPhone Settings plist to format the contents.
5. Control-click the editor pane again and choose Add Row to add a new key.
6. Add and configure any additional keys you need.
After adding a new Settings page to your Settings bundle, you can edit the page’s contents as described in
“Configuring a Settings Page: A Tutorial” (page 31). To display the settings for your page, you must reference
it from a child pane control as described in “Hierarchical Preferences” (page 27).
Implementing an iOS Settings Bundle
Creating and Modifying the Settings Bundle
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
33Note: In Xcode 4, adding a property-list file to your project does not automatically associate it with
your Settings bundle. You must use the Finder to move any additional property-list files into your
Settings bundle.
Debugging Preferences for Simulated Apps
When running your app, iOS Simulatorstores any preferences valuesfor your app in ~/Library/Application
Support/iOS Simulator/User/Applications//Library/Preferences, where
is a programmatically generated directory name that iOS uses to identify your app.
Each time you build your app, Xcode preserves your app preferences and other relevant library files. If you
want to remove the current preferences for testing purposes, you can delete the app from Simulator or choose
Reset Contents and Settings from the iOS Simulator menu.
Implementing an iOS Settings Bundle
Debugging Preferences for Simulated Apps
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
34This table describes the changes to Preferences and Settings Programming Guide .
Date Notes
2012-03-01 Updated the document to reflect new limits for key and value sizes.
Updated the document to include information about Settings bundles
and iOS in general. Also incorporated iCloud information.
2011-10-12
Removed the articles on storing NSColor objects and using Cocoa bindings
and now link to their locations instead.
Changed document name from User Defaults Programming Topics.
2007-10-31 Updated information about periodic autosave behavior.
2007-01-08 Corrected typos and capitalization mistakes.
Added overview of procedure forstoring non-property-list objectsin user
defaults, and linked to related article.
2006-11-07
2006-09-05 Made small additions to the content. Changed title from "User Defaults."
Expanded explanation of user defaults in introduction.
Noted requirement that a default’s value must be a property list value at
the beginning of the “Using NSUserDefaults” article.
Included an article that describes the use of NSUserDefaultsController.
Corrected minor typographical errors.
2005-08-11
2004-02-03 Added article “Storing NSColor in User Defaults”.
Linked to the Core Foundation Preferences Programming Topic, which
was also incorrectly named.
2003-05-09
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
35
Document Revision HistoryDate Notes
Added link in limitations area to CFPreferences. Corrected class name in
Defaults Domains Concept.
2003-01-13
Revision history was added to existing topic. It will be used to record
changes to the content of the topic.
2002-11-12
Document Revision History
2012-03-01 | © 2012 Apple Inc. All Rights Reserved.
36Apple Inc.
© 2012 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Cocoa, Finder, iPhone,
Mac, OS X, and Xcode are trademarks of Apple
Inc., registered in the U.S. and other countries.
.Mac and iCloud are service marks of Apple Inc.,
registered in the U.S. and other countries.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
OpenGL Programming
Guide for MacContents
About OpenGL for OS X 11
At a Glance 11
OpenGL Is a C-based, Platform-Neutral API 12
Different Rendering Destinations Require Different Setup Commands 12
OpenGL on Macs Exists in a Heterogenous Environment 12
OpenGL Helps Applications Harness the Power of Graphics Processors 13
Concurrency in OpenGL Applications Requires Additional Effort 13
Performance Tuning Allows Your Application to Provide an Exceptional User Experience 14
How to Use This Document 14
Prerequisites 15
See Also 15
OpenGL on the Mac Platform 17
OpenGL Concepts 17
OpenGL Implements a Client-Server Model 18
OpenGL Commands Can Be Executed Asynchronously 18
OpenGL Commands Are Executed In Order 19
OpenGL Copies Client Data at Call-Time 19
OpenGL Relies on Platform-Specific Libraries For Critical Functionality 19
OpenGL in OS X 20
Accessing OpenGL Within Your Application 21
OpenGL APIs Specific to OS X 22
Apple-Implemented OpenGL Libraries 23
Terminology 24
Renderer 24
Renderer and Buffer Attributes 24
Pixel Format Objects 24
OpenGL Profiles 25
Rendering Contexts 25
Drawable Objects 25
Virtual Screens 26
Offline Renderer 31
Running an OpenGL Program in OS X 31
Making Great OpenGL Applications on the Macintosh 33
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
2Drawing to a Window or View 35
General Approach 35
Drawing to a Cocoa View 36
Drawing to an NSOpenGLView Class: A Tutorial 37
Drawing OpenGL Content to a Custom View 40
Optimizing OpenGL for High Resolution 44
Enable High-Resolution Backing for an OpenGL View 44
Set Up the Viewport to Support High Resolution 45
Adjust Model and Texture Assets 46
Check for Calls Defined in Pixel Dimensions 46
Tune OpenGL Performance for High Resolution 47
Use a Layer-Backed View to Overlay Text on OpenGL Content 48
Use an Application Window for Fullscreen Operation 49
Convert the Coordinate Space When Hit Testing 49
Drawing to the Full Screen 50
Creating a Full-Screen Application 50
52
Drawing Offscreen 53
Rendering to a Framebuffer Object 53
Using a Framebuffer Object as a Texture 54
Using a Framebuffer Object as an Image 58
Rendering to a Pixel Buffer 60
Setting Up a Pixel Buffer for Offscreen Drawing 61
Using a Pixel Buffer as a Texture Source 61
Rendering to a Pixel Buffer on a Remote System 63
Choosing Renderer and Buffer Attributes 64
OpenGL Profiles (OS X v10.7) 64
Buffer Size Attribute Selection Tips 65
Ensuring That Back Buffer Contents Remain the Same 66
Ensuring a Valid Pixel Format Object 66
Ensuring a Specific Type of Renderer 67
Ensuring a Single Renderer for a Display 68
Allowing Offline Renderers 69
OpenCL 70
Deprecated Attributes 70
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
3
ContentsWorking with Rendering Contexts 72
Update the Rendering Context When the Renderer or Geometry Changes 72
Tracking Renderer Changes 73
Updating a Rendering Context for a Custom Cocoa View 73
Context Parameters Alter the Context’s Behavior 76
Swap Interval Allows an Application to Synchronize Updates to the Screen Refresh 76
Surface Opacity Specifies How the OpenGL Surface Blends with Surfaces Behind It 77
Surface Drawing Order Specifies the Position of the OpenGL Surface Relative to the Window 77
Determining Whether Vertex and Fragment Processing Happens on the GPU 78
Controlling the Back Buffer Size 78
Sharing Rendering Context Resources 79
Determining the OpenGL Capabilities Supported by the Renderer 83
Detecting Functionality 83
Guidelines for Code That Checks for Functionality 87
OpenGL Renderer Implementation-Dependent Values 88
OpenGL Application Design Strategies 89
Visualizing OpenGL 89
Designing a High-Performance OpenGL Application 91
Update OpenGL Content Only When Your Data Changes 94
Synchronize with the Screen Refresh Rate 96
Avoid Synchronizing and Flushing Operations 96
Using glFlush Effectively 97
Avoid Querying OpenGL State 98
Use Fences for Finer-Grained Synchronization 98
Allow OpenGL to Manage Your Resources 99
Use Double Buffering to Avoid Resource Conflicts 100
Be Mindful of OpenGL State Variables 101
Replace State Changes with OpenGL Objects 102
Use Optimal Data Types and Formats 102
Use OpenGL Macros 103
Best Practices for Working with Vertex Data 104
Understand How Vertex Data Flows Through OpenGL 105
Techniques for Handling Vertex Data 107
Vertex Buffers 107
Using Vertex Buffers 108
Buffer Usage Hints 110
Flush Buffer Range Extension 113
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
4
ContentsVertex Array Range Extension 113
Vertex Array Object 116
Best Practices for Working with Texture Data 118
Using Extensions to Improve Texture Performance 119
Pixel Buffer Objects 121
Apple Client Storage 124
Apple Texture Range and Rectangle Texture 125
Combining Client Storage with Texture Ranges 127
Optimal Data Formats and Types 128
Working with Non–Power-of-Two Textures 129
Creating Textures from Image Data 131
Creating a Texture from a Cocoa View 131
Creating a Texture from a Quartz Image Source 133
Getting Decompressed Raw Pixel Data from a Source Image 135
Downloading Texture Data 136
Double Buffering Texture Data 137
Customizing the OpenGL Pipeline with Shaders 139
Shader Basics 141
Advanced Shading Extensions 142
Transform Feedback 142
GPU Shader 4 143
Geometry Shaders 143
Uniform Buffers 143
Techniques for Scene Antialiasing 144
Guidelines 145
General Approach 145
Hinting for a Specific Antialiasing Technique 147
Concurrency and OpenGL 148
Identifying Whether an OpenGL Application Can Benefit from Concurrency 149
OpenGL Restricts Each Context to a Single Thread 149
Strategies for Implementing Concurrency in OpenGL Applications 150
Multithreaded OpenGL 150
Perform OpenGL Computations in a Worker Task 151
Use Multiple OpenGL Contexts 153
Guidelines for Threading OpenGL Applications 154
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
5
ContentsTuning Your OpenGL Application 155
Gathering and Analyzing Baseline Performance Data 156
Using OpenGL Driver Monitor to Measure Stalls 161
Identifying Bottlenecks with Shark 161
Legacy OpenGL Functionality by Version 163
Version 1.1 163
Version 1.2 164
Version 1.3 165
Version 1.4 165
Version 1.5 166
Version 2.0 166
Version 2.1 167
Updating an Application to Support the OpenGL 3.2 Core Specification 168
Removed Functionality 168
Extension Changes on OS X 169
Setting Up Function Pointers to OpenGL Routines 171
Obtaining a Function Pointer to an Arbitrary OpenGL Entry Point 171
Initializing Entry Points 172
Document Revision History 175
Glossary 179
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
6
ContentsFigures, Tables, and Listings
OpenGL on the Mac Platform 17
Figure 1-1 OpenGL provides the reflections in iChat 17
Figure 1-2 OpenGL client-server model 18
Figure 1-3 Graphics platform model 18
Figure 1-4 MacOS X OpenGL driver model 20
Figure 1-5 Layers of OpenGL for OS X 21
Figure 1-6 The programing interfaces used for OpenGL content 22
Figure 1-7 Data flow through OpenGL 26
Figure 1-8 A virtual screen displays what the user sees 27
Figure 1-9 Two virtual screens 28
Figure 1-10 A virtual screen can represent more than one physical screen 29
Figure 1-11 Two virtual screens and two graphics cards 30
Figure 1-12 The flow of data through OpenGL 31
Drawing to a Window or View 35
Figure 2-1 OpenGL content in a Cocoa view 35
Figure 2-2 The output from the Golden Triangle program 39
Listing 2-1 The interface for MyOpenGLView 37
Listing 2-2 Include OpenGL/gl.h 38
Listing 2-3 The drawRect: method for MyOpenGLView 38
Listing 2-4 Code that draws a triangle using OpenGL commands 38
Listing 2-5 The interface for a custom OpenGL view 40
Listing 2-6 The initWithFrame:pixelFormat: method 41
Listing 2-7 The lockFocus method 42
Listing 2-8 The drawRect method for a custom view 42
Listing 2-9 Detaching the context from a drawable object 43
Optimizing OpenGL for High Resolution 44
Figure 3-1 Enabling high-resolution backing for an OpenGL view 45
Figure 3-2 A text overlay scales automatically for standard resolution (left) and high resolution (right)
48
Listing 3-1 Setting up the viewport for drawing 45
Drawing to the Full Screen 50
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
7Figure 4-1 Drawing OpenGL content to the full screen 50
Drawing Offscreen 53
Listing 5-1 Setting up a framebuffer for texturing 57
Listing 5-2 Setting up a renderbuffer for drawing images 59
Choosing Renderer and Buffer Attributes 64
Table 6-1 Renderer types and pixel format attributes 67
Listing 6-1 Using the CGL API to create a pixel format object 66
Listing 6-2 Setting an NSOpenGLContext object to use a specific display 68
Listing 6-3 Setting a CGL context to use a specific display 69
Working with Rendering Contexts 72
Figure 7-1 A fixed size back buffer and variable size front buffer 79
Figure 7-2 Shared contexts attached to the same drawable object 80
Figure 7-3 Shared contexts and more than one drawable object 80
Listing 7-1 Handling context updates for a custom view 74
Listing 7-2 Using CGL to set up synchronization 76
Listing 7-3 Using CGL to set surface opacity 77
Listing 7-4 Using CGL to set surface drawing order 77
Listing 7-5 Using CGL to check whether the GPU is processing vertices and fragments 78
Listing 7-6 Using CGL to set up back buffer size control 79
Listing 7-7 Setting up an NSOpenGLContext object for sharing 81
Listing 7-8 Setting up a CGL context for sharing 82
Determining the OpenGL Capabilities Supported by the Renderer 83
Table 8-1 Common OpenGL renderer limitations 88
Table 8-2 OpenGL shader limitations 88
Listing 8-1 Checking for OpenGL functionality 84
Listing 8-2 Setting up a valid rendering context to get renderer functionality information 86
OpenGL Application Design Strategies 89
Figure 9-1 OpenGL graphics pipeline 90
Figure 9-2 OpenGL client-server architecture 91
Figure 9-3 Application model for managing resources 92
Figure 9-4 Single-buffered vertex array data 100
Figure 9-5 Double-buffered vertex array data 101
Listing 9-1 Setting up a Core Video display link 94
Listing 9-2 Setting up synchronization 96
Listing 9-3 Disabling state variables 102
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
8
Figures, Tables, and ListingsListing 9-4 Using CGL macros 103
Best Practices for Working with Vertex Data 104
Figure 10-1 Vertex data sets can be quite large 104
Figure 10-2 Vertex data path 105
Figure 10-3 Immediate mode requires a copy of the current vertex data 105
Listing 10-1 Submitting vertex data using glDrawElements. 106
Listing 10-2 Using the vertex buffer object extension with dynamic data 109
Listing 10-3 Using the vertex buffer object extension with static data 110
Listing 10-4 Geometry with different usage patterns 111
Listing 10-5 Using the vertex array range extension with dynamic data 115
Listing 10-6 Using the vertex array range extension with static data 116
Best Practices for Working with Texture Data 118
Figure 11-1 Textures add realism to a scene 118
Figure 11-2 Texture data path 119
Figure 11-3 Data copies in an OpenGL program 120
Figure 11-4 The client storage extension eliminates a data copy 124
Figure 11-5 The texture range extension eliminates a data copy 126
Figure 11-6 Combining extensions to eliminate data copies 127
Figure 11-7 Normalized and non-normalized coordinates 129
Figure 11-8 An image segmented into power-of-two tiles 130
Figure 11-9 Using an image as a texture for a cube 131
Figure 11-10 Single-buffered data 137
Figure 11-11 Double-buffered data 138
Listing 11-1 Using texture extensions for a rectangular texture 127
Listing 11-2 Using texture extensions for a power-of-two texture 128
Listing 11-3 Building an OpenGL texture from an NSView object 132
Listing 11-4 Using a Quartz image as a texture source 134
Listing 11-5 Getting pixel data from a source image 135
Listing 11-6 Code that downloads texture data 136
Customizing the OpenGL Pipeline with Shaders 139
Figure 12-1 OpenGL fixed-function pipeline 139
Figure 12-2 OpenGL shader pipeline 140
Listing 12-1 Loading a Shader 141
Techniques for Scene Antialiasing 144
Table 13-1 Antialiasing hints 147
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
9
Figures, Tables, and ListingsConcurrency and OpenGL 148
Figure 14-1 CPU processing and OpenGL on separate threads 152
Figure 14-2 Two contexts on separate threads 153
Listing 14-1 Enabling the multithreaded OpenGL engine 151
Tuning Your OpenGL Application 155
Figure 15-1 Output produced by the top application 157
Figure 15-2 The OpenGL Profiler window 158
Figure 15-3 A statistics window 159
Figure 15-4 A Trace window 160
Figure 15-5 The graph view in OpenGL Driver Monitor 161
Legacy OpenGL Functionality by Version 163
Table A-1 Functionality added in OpenGL 1.1 163
Table A-2 Functionality added in OpenGL 1.2 164
Table A-3 Functionality added in OpenGL 1.3 165
Table A-4 Functionality added in OpenGL 1.4 165
Table A-5 Functionality added in OpenGL 1.5 166
Table A-6 Functionality added in OpenGL 2.0 166
Table A-7 Functionality added in OpenGL 2.1 167
Updating an Application to Support the OpenGL 3.2 Core Specification 168
Table B-1 Extensions described in this guide 169
Setting Up Function Pointers to OpenGL Routines 171
Listing C-1 Using NSLookupAndBindSymbol to obtain a symbol for a symbol name 172
Listing C-2 Using NSGLGetProcAddress to obtain an OpenGL entry point 173
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
10
Figures, Tables, and ListingsOpenGL is an open, cross-platform graphics standard with broad industry support. OpenGL greatly eases the
task of writing real-time 2D or 3D graphics applications by providing a mature, well-documented graphics
processing pipeline that supports the abstraction of current and future hardware accelerators.
OpenGL client
OpenGL server
Graphics hardware
Application
OpenGL framework
OpenGL driver
Runs on GPU
Runs on CPU
At a Glance
OpenGL is an excellent choice for graphics development on the Macintosh platform because it offers the
following advantages:
● Reliable Implementation. The OpenGL client-server model abstracts hardware details and guarantees
consistent presentation on any compliant hardware and software configuration. Every implementation of
OpenGL adheres to the OpenGL specification and must pass a set of conformance tests.
● Performance. Applications can harness the considerable power of the graphics hardware to improve
rendering speeds and quality.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
11
About OpenGL for OS X●
Industry acceptance. The specification for OpenGL is controlled by the Khronos Group, an industry
consortium whose members include many of the major companies in the computer graphics industry,
including Apple. In addition to OpenGL for OS X, there are OpenGL implementations for Windows, Linux,
Irix, Solaris, and many game consoles.
OpenGL Is a C-based, Platform-Neutral API
Because OpenGL is a C-based API, it is extremely portable and widely supported. As a C API, it integrates
seamlessly with Objective-C based Cocoa applications. OpenGL provides functions your application uses to
generate 2D or 3D images. Your application presents the rendered images to the screen or copies them back
to its own memory.
The OpenGL specification does not provide a windowing layer of its own. It relies on functions defined by OS
X to integrate OpenGL drawing with the windowing system. Your application creates an OS X OpenGL rendering
context and attaches a rendering target to it (known as a drawable object). The rendering context manages
OpenGL state changes and objects created by calls to the OpenGL API. The drawable object is the final
destination for OpenGL drawing commands and is typically associated with a Cocoa window or view.
Relevant Chapters: “OpenGL on the Mac Platform” (page 17)
Different Rendering Destinations Require Different Setup Commands
Depending on whether your application intends to draw OpenGL content to a window, to draw to the entire
screen, or to perform offscreen image processing, it takes different steps to create the rendering context and
associate it with a drawable object.
Relevant Chapters: “Drawing to a Window or View” (page 35), “Drawing to the Full Screen” (page
50) and “Drawing Offscreen” (page 53)
OpenGL on Macs Exists in a Heterogenous Environment
Macs support different types of graphics processors, each with different rendering capabilities, supporting
versions of OpenGL from 1.x through OpenGL 3.2. When creating a rendering context, your application can
accept a broad range of renderers or it can restrict itself to devices with specific capabilities. Once you have a
context, you can configure how that context executes OpenGL commands.
About OpenGL for OS X
At a Glance
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
12OpenGL on the Mac is not only a heterogenous environment, but it is also a dynamic environment. Users can
add or remove displays, or take a laptop running on battery power and plug it into a wall. When the graphics
environment on the Mac changes, the renderer associated with the context may change. Your application
must handle these changes and adjust how it uses OpenGL.
Relevant Chapters: “Choosing Renderer and Buffer Attributes” (page 64), “Working with Rendering
Contexts” (page 72), and “Determining the OpenGL Capabilities Supported by the Renderer” (page
83)
OpenGL Helps Applications Harness the Power of Graphics Processors
Graphics processors are massively parallelized devices optimized for graphics operations. To access that
computing power adds additional overhead because data must move from your application to the GPU over
slower internal buses. Accessing the same data simultaneously from both your application and OpenGL is
usually restricted. To get great performance in your application, you must carefully design your application to
feed data and commands to OpenGL so that the graphics hardware runs in parallel with your application. A
poorly tuned application may stall either on the CPU or the GPU waiting for the other to finish processing.
When you are ready to optimize your application’s performance, Apple provides both general-purpose and
OpenGL-specific profiling tools that make it easy to learn where your application spends its time.
Relevant Chapters: “Optimizing OpenGL for High Resolution” (page 44), “OpenGL on the Mac
Platform” (page 17),“OpenGL Application Design Strategies” (page 89), “Best Practices for Working
with Vertex Data” (page 104), “Best Practicesfor Working with Texture Data” (page 118), “Customizing
the OpenGL Pipeline with Shaders” (page 139), and “Tuning Your OpenGL Application” (page 155)
Concurrency in OpenGL Applications Requires Additional Effort
Many Macs ship with multiple processors or multiple cores, and future hardware is expected to add more of
each. Designing applications to take advantage of multiprocessing is critical. OpenGL places additional
restrictions on multithreaded applications. If you intend to add concurrency to an OpenGL application, you
must ensure that the application does not access the same context from two different threads at the same
time.
About OpenGL for OS X
At a Glance
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
13Relevant Chapters: “Concurrency and OpenGL” (page 148)
Performance Tuning Allows Your Application to Provide an Exceptional User
Experience
Once you’ve improved the performance of your OpenGL application and taken advantage of concurrency, put
some of the freed processing power to work for you. Higher resolution textures, detailed models, and more
complex lighting and shading algorithms can improve image quality. Full-scene antialiasing on modern graphics
hardware can eliminate many of the “jaggies” common on lower resolution images.
Relevant Chapters: “Customizing the OpenGL Pipeline with Shaders” (page 139),“Techniques for
Scene Antialiasing” (page 144)
How to Use This Document
If you have never programmed in OpenGL on the Mac, you should read this book in its entirety, starting with
“OpenGL on the Mac Platform” (page 17). Critical Mac terminology is defined in that chapter as well as in the
“Glossary” (page 179).
If you already have an OpenGL application running on the Mac, but have not yet updated it for OS X v10.7,
read “Choosing Renderer and Buffer Attributes” (page 64) to learn how to choose an OpenGL profile for your
application.
To find out how to update an existing OpenGL app for high resolution, see “Optimizing OpenGL for High
Resolution” (page 44).
Once you have OpenGL content in your application, read “OpenGL Application Design Strategies” (page 89)
to learn fundamental patterns for implementing high-performance OpenGL applications, and the chapters
that follow to learn how to apply those patterns to specific OpenGL problems.
About OpenGL for OS X
How to Use This Document
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
14Important: Although this guide describes how to create rendering contexts that support OpenGL 3.2,
most code examples and discussion in the rest of the book describe the earlier legacy versions of OpenGL.
See “Updating an Application to Support the OpenGL 3.2 Core Specification” (page 168) for more information
on migrating your application to OpenGL 3.2.
Prerequisites
This guide assumes that you have some experience with OpenGL programming, but want to learn how to
apply that knowledge to create software for the Mac. Although this guide provides advice on optimizing
OpenGL code, it does not provide entry-level information on how to use the OpenGL API. If you are unfamiliar
with OpenGL, you should read “OpenGL on the Mac Platform” (page 17) to get an overview of OpenGL on the
Mac platform, and then read the following OpenGL programming guide and reference documents:
● OpenGL Programming Guide, by Dave Shreiner and the Khronos OpenGL Working Group; otherwise known
as "The Red book.”
● OpenGL Shading Language , by Randi J. Rost, is an excellent guide for those who want to write programs
that compute surface properties (also known as shaders).
● OpenGL Reference Pages.
Before reading this document, you should be familiar with Cocoa windows and views asintroduced in Window
Programming Guide and View Programming Guide .
See Also
Keep these reference documents handy as you develop your OpenGL program for OS X:
● NSOpenGLView Class Reference , NSOpenGLContext Class Reference , NSOpenGLPixelBuffer Class Reference ,
and NSOpenGLPixelFormat Class Reference provide a complete description of the classes and methods
needed to integrate OpenGL content into a Cocoa application.
● CGL Reference describes low-level functions that can be used to create full-screen OpenGL applications.
● OpenGL Extensions Guide provides information about OpenGL extensions supported in OS X.
The OpenGL Foundation website, http://www.opengl.org, provides information on OpenGL commands, the
Khronos OpenGL Working Group, logo requirements, OpenGL news, and many other topics. It's a site that
you'll want to visit regularly. Among the many resources it provides, the following are important reference
documents for OpenGL developers:
About OpenGL for OS X
Prerequisites
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
15● OpenGL Specification provides detailed information on how an OpenGL implementation is expected to
handle each OpenGL command.
● OpenGL Reference describes the main OpenGL library.
● OpenGL GLU Reference describes the OpenGL Utility Library, which contains convenience functions
implemented on top of the OpenGL API.
● OpenGL GLUT Reference describes the OpenGL Utility Toolkit, a cross-platform windowing API.
● OpenGL API Code and Tutorial Listings provides code examples for fundamental tasks, such as modeling
and texture mapping, as well as for advanced techniques, such as high dynamic range rendering (HDRR).
About OpenGL for OS X
See Also
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
16You can tell that Apple has an implementation of OpenGL on its platform by looking at the user interface for
many of the applications that are installed with OS X. The reflections built into iChat (Figure 1-1) provide one
of the more notable examples. The responsiveness of the windows, the instant results of applying an effect in
iPhoto, and many other operations in OS X are due to the use of OpenGL. OpenGL is available to all Macintosh
applications.
OpenGL for OS X is implemented as a set of frameworks that contain the OpenGL runtime engine and its
drawing software. These frameworks use platform-neutral virtual resourcesto free your programming as much
as possible from the underlying graphics hardware. OS X provides a set of application programming interfaces
(APIs) that Cocoa applications can use to support OpenGL drawing.
Figure 1-1 OpenGL provides the reflections in iChat
This chapter provides an overview of OpenGL and the interfaces your application uses on the Mac platform to
tap into it.
OpenGL Concepts
To understand how OpenGL fits into OS X and your application, you should first understand how OpenGL is
designed.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
17
OpenGL on the Mac PlatformOpenGL Implements a Client-Server Model
OpenGL uses a client-server model, as shown in Figure 1-2. When your application calls an OpenGL function,
it talks to an OpenGL client. The client delivers drawing commands to an OpenGL server. The nature of the
client, the server, and the communication path between them is specific to each implementation of OpenGL.
For example, the server and clients could be on different computers, or they could be different processes on
the same computer.
Figure 1-2 OpenGL client-server model
Application
OpenGL client
OpenGL server
A client-server model allows the graphics workload to be divided between the client and the server. For
example, all Macintosh computersship with dedicated graphics hardware that is optimized to perform graphics
calculations in parallel. Figure 1-3 shows a common arrangement of CPUs and GPUs. With this hardware
configuration, the OpenGL client executes on the CPU and the server executes on the GPU.
Figure 1-3 Graphics platform model
CPU
RAM
Core Core
GPU
RAM
Core Core Core
Core Core Core
System
OpenGL Commands Can Be Executed Asynchronously
A benefit of the OpenGL client-server model is that the client can return control to the application before the
command has finished executing. An OpenGL client may also buffer or delay execution of OpenGL commands.
If OpenGL required all commands to complete before returning control to the application, then either the CPU
or the GPU would be idle waiting for the other to provide it data, resulting in reduced performance.
OpenGL on the Mac Platform
OpenGL Concepts
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
18Some OpenGL commandsimplicitly or explicitly require the client to wait untilsome or all previously submitted
commands have completed. OpenGL applicationsshould be designed to reduce the frequency of client-server
synchronizations. See “OpenGL Application Design Strategies” (page 89) for more information on how to
design your OpenGL application.
OpenGL Commands Are Executed In Order
OpenGL guarantees that commands are executed in the order they are received by OpenGL.
OpenGL Copies Client Data at Call-Time
When an application calls an OpenGL function, the OpenGL client copies any data provided in the parameters
before returning control to the application. For example, if a parameter points at an array of vertex data stored
in application memory, OpenGL must copy that data before returning. Therefore, an application is free to
change memory it owns regardless of calls it makes to OpenGL.
The data that the client copies is often reformatted before it is transmitted to the server. Copying, modifying,
and transmitting parameters to the server adds overhead to calling OpenGL. Applications should be designed
to minimize copy overhead.
OpenGL Relies on Platform-Specific Libraries For Critical Functionality
OpenGL provides a rich set of cross-platform drawing commands, but does not define functions to interact
with an operating system’s graphics subsystem. Instead, OpenGL expects each implementation to define an
interface to create rendering contexts and associate them with the graphics subsystem. A rendering context
holds all of the data stored in the OpenGL state machine. Allowing multiple contexts allows the state in one
machine to be changed by an application without affecting other contexts.
Associating OpenGL with the graphic subsystem usually means allowing OpenGL content to be rendered to
a specific window. When content is associated with a window, the implementation creates whatever resources
are required to allow OpenGL to render and display images.
OpenGL on the Mac Platform
OpenGL Concepts
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
19OpenGL in OS X
OpenGL in OS X implementsthe OpenGL client-server model using a common OpenGL framework and plug-in
drivers. The framework and driver combine to implement the client portion of OpenGL, as shown in Figure
1-4. Dedicated graphics hardware provides the server. Although this is the common scenario, Apple also
provides a software renderer implemented entirely on the CPU.
Figure 1-4 MacOS X OpenGL driver model
OpenGL client
OpenGL server
Graphics hardware
Application
OpenGL framework
OpenGL driver
Runs on GPU
Runs on CPU
OS X supports a display space that can include multiple dissimilar displays, each driven by different graphics
cards with different capabilities. In addition, multiple OpenGL renderers can drive each graphics card. To
accommodate this versatility, OpenGL for OS X is segmented into well-defined layers: a window system layer,
OpenGL on the Mac Platform
OpenGL in OS X
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
20a framework layer, and a driver layer, as shown in Figure 1-5. This segmentation allows for plug-in interfaces
to both the window system layer and the framework layer. Plug-in interfaces offer flexibility in software and
hardware configuration without violating the OpenGL standard.
Figure 1-5 Layers of OpenGL for OS X
Software GLD plug-in ATI GLD plug-in NVIDIA GLD plug-in Intel GLD plug-in
Application
Hardware
Window system layer
Common OpenGL framework
Driver layer
NSOpenGL CGL OpenGL
The window system layer is an OS X–specific layer that your application uses to create OpenGL rendering
contexts and associate them with the OS X windowing system. The NSOpenGL classes and Core OpenGL (CGL)
API also provide some additional controlsfor how OpenGL operates on that context. See “OpenGL APIs Specific
to OS X” (page 22) for more information. Finally, this layer also includes the OpenGL libraries—GL, GLU, and
GLUT. (See “Apple-Implemented OpenGL Libraries” (page 23) for details.)
The common OpenGL framework layer is the software interface to the graphics hardware. This layer contains
Apple's implementation of the OpenGL specification.
The driver layer contains the optional GLD plug-in interface and one or more GLD plug-in drivers, which may
have different software and hardware support capabilities. The GLD plug-in interface supports third-party
plug-in drivers, allowing third-party hardware vendors to provide drivers optimized to take best advantage of
their graphics hardware.
Accessing OpenGL Within Your Application
The programming interfacesthat your application callsfall into two categories—those specific to the Macintosh
platform and those defined by the OpenGL Working Group. The Apple-specific programming interfaces are
what Cocoa applications use to communicate with the OS X windowing system. These APIs don't create OpenGL
content, they manage content, direct it to a drawing destination, and control various aspects of the rendering
OpenGL on the Mac Platform
Accessing OpenGL Within Your Application
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
21operation. Your application calls the OpenGL APIs to create content. OpenGL routines accept vertex, pixel, and
texture data and assemble the data to create an image. The final image resides in a framebuffer, which is
presented to the user through the windowing-system specific API.
Figure 1-6 The programing interfaces used for OpenGL content
OpenGL engine and drivers
GLUT
CGL OpenGL
NSOpenGL
classes
GLUT application Cocoa application
OpenGL APIs Specific to OS X
OS X offers two easy-to-use APIs that are specific to the Macintosh platform: the NSOpenGL classes and the
CGL API. Throughout this document, these APIs are referred to as the Apple-specific OpenGL APIs.
Cocoa provides many classes specifically for OpenGL:
● The NSOpenGLContext class implements a standard OpenGL rendering context.
● The NSOpenGLPixelFormat class is used by an application to specify the parameters used to create the
OpenGL context.
● The NSOpenGLView class is a subclass of NSView that uses NSOpenGLContext and
NSOpenGLPixelFormat to display OpenGL content in a view. Applicationsthatsubclass NSOpenGLView
do not need to directly subclass NSOpenGLPixelFormat or NSOpenGLContext. Applications that need
customization or flexibility, can subclass NSView and create NSOpenGLPixelFormat and
NSOpenGLContext objects manually.
● The NSOpenGLLayer class allows your application to integrate OpenGL drawing with Core Animation.
● The NSOpenGLPixelBuffer class provides hardware-accelerated offscreen drawing.
OpenGL on the Mac Platform
Accessing OpenGL Within Your Application
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
22The Core OpenGL API (CGL) residesin the OpenGL framework and is used to implement the NSOpenGL classes.
CGL offersthe most direct accessto system functionality and providesthe highest level of graphics performance
and control for drawing to the full screen. CGL Reference provides a complete description of this API.
Apple-Implemented OpenGL Libraries
OS X also provides the full suite of graphics libraries that are part of every implementation of OpenGL: GL, GLU,
GLUT, and GLX. Two of these—GL and GLU—provide low-level drawing support. The other two—GLUT and
GLX—support drawing to the screen.
Your application typically interfaces directly with the core OpenGL library (GL), the OpenGL Utility library (GLU),
and the OpenGL Utility Toolkit (GLUT). The GL library provides a low-level modular API that allows you to
define graphical objects. Itsupportsthe core functions defined by the OpenGL specification. It providessupport
for two fundamental types of graphics primitives: objects defined by sets of vertices, such as line segments
and simple polygons, and objects that are pixel-based images, such as filled rectangles and bitmaps. The GL
API does not handle complex custom graphical objects; your application must decompose them into simpler
geometries.
The GLU library combines functions from the GL library to support more advanced graphics features. It runs
on all conforming implementations of OpenGL. GLU is capable of creating and handling complex polygons
(including quartic equations), processing nonuniform rational b-spline curves (NURBs), scaling images, and
decomposing a surface to a series of polygons (tessellation).
The GLUT library provides a cross-platform API for performing operations associated with the user windowing
environment—displaying and redrawing content, handling events, and so on. It isimplemented on most UNIX,
Linux, and Windows platforms. Code that you write with GLUT can be reused across multiple platforms. However,
such code is constrained by a generic set of user interface elements and event-handling options. This document
does not show how to use GLUT. The GLUTBasics sample project shows you how to get started with GLUT.
GLX is an OpenGL extension that supports using OpenGL within a window provided by the X Window system.
X11 for OS X is available as an optional installation. (It's not shown in Figure 1-6 (page 22).) See OpenGL
Programming for the X Window System, published by Addison Wesley for more information.
This document does not show how to use these libraries. For detailed information, either go to the OpenGL
Foundation website http://www.opengl.org or see the most recent version of "The Red book"—OpenGL Programming Guide, published by Addison Wesley.
OpenGL on the Mac Platform
Accessing OpenGL Within Your Application
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
23Terminology
There are a number of termsthat you’ll want to understand so that you can write code effectively using OpenGL:
renderer, renderer attributes, buffer attributes, pixel format objects, rendering contexts, drawable objects, and
virtual screens. As an OpenGL programmer, some of these may seem familiar to you. However, understanding
the Apple-specific nuances of these terms will help you get the most out of OpenGL on the Macintosh platform.
Renderer
A renderer isthe combination of the hardware and software that OpenGL usesto execute OpenGL commands.
The characteristics of the final image depend on the capabilities of the graphics hardware associated with the
renderer and the device used to display the image. OS X supports graphics accelerator cards with varying
capabilities, as well as a software renderer. It is possible for multiple renderers, each with different capabilities
or features, to drive a single set of graphics hardware. To learn how to determine the exact features of a
renderer, see “Determining the OpenGL Capabilities Supported by the Renderer” (page 83).
Renderer and Buffer Attributes
Your application uses renderer and buffer attributes to communicate renderer and buffer requirements to
OpenGL. The Apple implementation of OpenGL dynamically selectsthe best renderer for the current rendering
task and doesso transparently to your application. If your application has very specific rendering requirements
and wants to control renderer selection, it can do so by supplying the appropriate renderer attributes. Buffer
attributes describe such things as color and depth buffer sizes, and whether the data is stereoscopic or
monoscopic.
Renderer and buffer attributes are represented by constants defined in the Apple-specific OpenGL APIs. OpenGL
uses the attributes you supply to perform the setup work needed prior to drawing content. “Drawing to a
Window or View” (page 35) provides a simple example that shows how to use renderer and buffer attributes.
“Choosing Renderer and Buffer Attributes” (page 64) explains how to choose renderer and buffer attributes
to achieve specific rendering goals.
Pixel Format Objects
A pixel format describes the format for pixel data storage in memory. The description includes the number
and order of components as well as their names (typically red, blue, green and alpha). It also includes other
information, such as whether a pixel contains stencil and depth values. A pixel format object is an opaque
data structure that holds a pixel format along with a list of renderers and display devices that satisfy the
requirements specified by an application.
OpenGL on the Mac Platform
Terminology
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
24Each of the Apple-specific OpenGL APIs defines a pixel format data type and accessor routines that you can
use to obtain the information referenced by this object. See “Virtual Screens” (page 26) for more information
on renderer and display devices.
OpenGL Profiles
OpenGL profiles are new in OS X 10.7. An OpenGL profile is a renderer attribute used to request a specific
version of the OpenGL specification. When your application provides an OpenGL profile as part of its renderer
attributes, it only receives renderers that provide the complete feature set promised by that profile. The render
can implement a different version of the OpenGL so long asthe version itsuppliesto your application provides
the same functionality that your application requested.
Rendering Contexts
A rendering context, or simply context, contains OpenGL state information and objects for your application.
State variables include such things as drawing color, the viewing and projection transformations, lighting
characteristics, and material properties. State variables are set per context. When your application creates
OpenGL objects (for example, textures), these are also associated with the rendering context.
Although your application can maintain more than one context, only one context can be the current context
in a thread. The current context is the rendering context that receives OpenGL commands issued by your
application.
Drawable Objects
A drawable object refers to an object allocated by the windowing system that can serve as an OpenGL
framebuffer. A drawable object is the destination for OpenGL drawing operations. The behavior of drawable
objects is not part of the OpenGL specification, but is defined by the OS X windowing system.
A drawable object can be any of the following: a Cocoa view, offscreen memory, a full-screen graphics device,
or a pixel buffer.
OpenGL on the Mac Platform
Terminology
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
25Note: A pixel buffer (pbuffer) is an OpenGL buffer designed for hardware-accelerated offscreen
drawing and as a source for texturing. An application can render an image into a pixel buffer and
then use the pixel buffer as a texture for other OpenGL commands. Although pixel buffers are
supported on Apple’s implementation of OpenGL, Apple recommends you use framebuffer objects
instead. See “Drawing Offscreen” (page 53) for more information on offscreen rendering.
Before OpenGL can draw to a drawable object, the object must be attached to a rendering context. The
characteristics of the drawable object narrow the selection of hardware and software specified by the rendering
context. Apple’s OpenGL automatically allocates buffers, creates surfaces, and specifies which renderer is the
current renderer.
The logical flow of data from an application through OpenGL to a drawable object is shown in Figure 1-7. The
application issues OpenGL commands that are sent to the current rendering context. The current context,
which contains state information, constrains how the commands are interpreted by the appropriate renderer.
The renderer converts the OpenGL primitives to an image in the framebuffer. (See also “Running an OpenGL
Program in OS X ” (page 31).)
Figure 1-7 Data flow through OpenGL
Rendered Image
Application
Possible renderers
OpenGL
buffers
Current
Drawable
objects
CONTEXT
Virtual Screens
The characteristics and quality of the OpenGL content that the user sees depend on both the renderer and
the physical display used to view the content. The combination of renderer and physical display is called a
virtual screen. This important concept has implications for any OpenGL application running on OS X.
A simple system, with one graphics card and one physical display, typically has two virtual screens. One virtual
screen consists of a hardware-based renderer and the physical display and the other virtual screen consists of
a software-based renderer and the physical display. OS X provides a software-based renderer as a fallback. It's
possible for your application to decline the use of thisfallback. You'llsee how in “Choosing Renderer and Buffer
Attributes” (page 64).
OpenGL on the Mac Platform
Terminology
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
26The green rectangle around the OpenGL image in Figure 1-8 surrounds a virtual screen for a system with one
graphics card and one display. Note that a virtual screen is not the physical display, which is why the green
rectangle is drawn around the application window thatshowsthe OpenGL content. In this case, it isthe renderer
provided by the graphics card combined with the characteristics of the display.
Figure 1-8 A virtual screen displays what the user sees
Graphics card
Virtual screen
OpenGL on the Mac Platform
Terminology
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
27Because a virtual screen is not simply the physical display, a system with one display can use more than one
virtualscreen at a time, asshown in Figure 1-9. The green rectangles are drawn to point out each virtualscreen.
Imagine that the virtual screen on the right side uses a software-only renderer and that the one on the left
uses a hardware-dependent renderer. Although this is a contrived example, it illustrates the point.
Figure 1-9 Two virtual screens
Graphics card
Virtual screen 2
(Software renderer)
Virtual screen 1
(Hardware renderer)
OpenGL on the Mac Platform
Terminology
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
28It's also possible to have a virtualscreen that can represent more than one physical display. The green rectangle
in Figure 1-10 is drawn around a virtual screen that spans two physical displays. In this case, the same graphics
hardware drives a pair of identical displays. A mirrored display also has a single virtual screen associated with
multiple physical displays.
Figure 1-10 A virtual screen can represent more than one physical screen
Dual-headed
graphics card
Identical displays Virtual screen
The concept of a virtualscreen is particularly important when the user drags an image from one physicalscreen
to another. When this happens, the virtual screen may change, and with it, a number of attributes of the
imaging process, such as the current renderer, may change. With the dual-headed graphics card shown in
Figure 1-10 (page 29), dragging between displays preserves the same virtual screen. However, Figure 1-11
shows the case for which two displays represent two unique virtual screens. Not only are the two graphics
cards different, but it's possible that the renderer, buffer attributes, and pixel characteristics are different. A
change in any of these three items can result in a change in the virtual screen.
OpenGL on the Mac Platform
Terminology
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
29When the user drags an image from one display to another, and the virtualscreen isthe same for both displays,
the image quality should appear similar. However, for the case shown in Figure 1-11, the image quality can be
quite different.
Figure 1-11 Two virtual screens and two graphics cards
Graphics card 1
Graphics card 2
Virtual screen 1 Virtual screen 2
OpenGL for OS X transparently manages rendering across multiple monitors. A user can drag a window from
one monitor to another, even though their display capabilities may be different or they may be driven by
dissimilar graphics cards with dissimilar resolutions and color depths.
OpenGL dynamically switches renderers when the virtual screen that contains the majority of the pixels in an
OpenGL window changes. When a window issplit between multiple virtualscreens, the framebuffer israsterized
entirely by the renderer driving the screen that contains the largest segment of the window. The regions of
the window on the other virtual screens are drawn by copying the rasterized image. When the entire OpenGL
drawable object is displayed on one virtual screen, there is no performance impact from multiple monitor
support.
Applications need to track virtual screen changes and, if appropriate, update the current application state to
reflect changes in renderer capabilities. See “Working with Rendering Contexts” (page 72).
OpenGL on the Mac Platform
Terminology
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
30Offline Renderer
An offline renderer is one that is not currently associated with a display. For example, a graphics processor
might be powered down to conserve power, or there might not be a display hooked up to the graphics card.
Offline renderers are not normally visible to your application, but your application can enable them by adding
the appropriate renderer attribute. Taking advantage of offline renderers is useful because it gives the user a
seamless experience when they plug in or remove displays.
For more information about configuring a context to see offline renderers, see “Choosing Renderer and Buffer
Attributes” (page 64). To enable your application to switch to a renderer when a display is attached,see “Update
the Rendering Context When the Renderer or Geometry Changes” (page 72).
Running an OpenGL Program in OS X
Figure 1-12 shows the flow of data in an OpenGL program, regardless of the platform that the program runs
on.
Figure 1-12 The flow of data through OpenGL
Rasterization
Fragment shading
and per-fragment
operations
Per-pixel
operations
Texture
assembly
Framebuffer
Vertex shading
and per-vertex
operations
Pixel data
Vertex data
Per-vertex operations include such things as applying transformation matrices to add perspective or to clip,
and applying lighting effects. Per-pixel operations include such things as color conversion and applying blur
and distortion effects. Pixels destined for textures are sent to texture assembly, where OpenGL stores textures
until it needs to apply them onto an object.
OpenGL rasterizesthe processed vertex and pixel data, meaning that the data are converged to create fragments.
A fragment encapsulates all the values for a pixel, including color, depth, and sometimes texture values. These
values are used during antialiasing and any other calculations needed to fill shapes and to connect vertices.
OpenGL on the Mac Platform
Running an OpenGL Program in OS X
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
31Per-fragment operations include applying environment effects, depth and stencil testing, and performing
other operations such as blending and dithering. Some operations—such as hidden-surface removal—end
the processing of a fragment. OpenGL draws fully processed fragments into the appropriate location in the
framebuffer.
The dashed arrows in Figure 1-12 indicate reading pixel data back from the framebuffer. They represent
operations performed byOpenGL functionssuch as glReadPixels, glCopyPixels, and glCopyTexImage2D.
So far you've seen how OpenGL operates on any platform. But how do Cocoa applications provide data to the
OpenGL for processing? A Mac application must perform these tasks:
● Set up a list of buffer and renderer attributes that define the sort of drawing you want to perform. (See
“Renderer and Buffer Attributes” (page 24).)
● Request the system to create a pixel format object that contains a pixel format that meets the constraints
of the buffer and render attributes and a list of all suitable combinations of displays and renderers. (See
“Pixel Format Objects” (page 24) and “Virtual Screens” (page 26).)
● Create a rendering context to hold state information that controls such things as drawing color, view and
projection matrices, characteristics of light, and conventions used to pack pixels. When you set up this
context, you must provide a pixel format object because the rendering context needs to know the set of
virtual screens that can be used for drawing. (See “Rendering Contexts” (page 25).)
● Bind a drawable object to the rendering context. The drawable object is what capturesthe OpenGL drawing
sent to that rendering context. (See “Drawable Objects” (page 25).)
● Make the rendering context the current context. OpenGL automatically targets the current context.
Although your application might have several rendering contexts set up, only the current one is the active
one for drawing purposes.
●
Issue OpenGL drawing commands.
● Flush the contents of the rendering context. This causes previously submitted commands to be rendered
to the drawable object and displays them to the user.
The tasks described in the first five bullet items are platform-specific. “Drawing to a Window or View” (page
35) provides simple examples of how to perform them. As you read other parts of this document, you'll see
there are a number of other tasks that, although not mandatory for drawing, are really quite necessary for any
application that wantsto use OpenGL to perform complex 3D drawing efficiently on a wide variety of Macintosh
systems.
OpenGL on the Mac Platform
Running an OpenGL Program in OS X
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
32Making Great OpenGL Applications on the Macintosh
OpenGL lets you create applications with outstanding graphics performance as well as a great user
experience—but neither of these things come for free. Your application performs best when it works with
OpenGL rather than against it. With that in mind, here are guidelines you should follow to create
high-performance, future-looking OpenGL applications:
● Ensure your application runs successfully with offline renderers and multiple graphics cards.
Apple ships many sophisticated hardware configurations. Your application should handle renderer changes
seamlessly. You should test your application on a Mac with multiple graphics processors and include tests
for attaching and removing displays. For more information on how to implement hot plugging correctly,
see “Working with Rendering Contexts” (page 72)
● Avoid finishing and flushing operations.
Pay particular attention to OpenGL functions that force previously submitted commands to complete.
Synchronizing the graphics hardware to the CPU may result in dramatically lower performance. Performance
is covered in detail in “OpenGL Application Design Strategies” (page 89).
● Use multithreading to improve the performance of your OpenGL application.
Many Macs support multiple simultaneous threads of execution. Your application should take advantage
of concurrency. Well-behaved applications can take advantage of concurrency in just a few line of code.
See “Concurrency and OpenGL” (page 148).
● Use buffer objects to manage your data.
Vertex buffer objects (VBOs) allow OpenGL to manage your application’s vertex data. Using vertex buffer
objects gives OpenGL more opportunities to cache vertex data in a format that is friendly to the graphics
hardware, improving application performance. For more information see “Best Practices for Working with
Vertex Data” (page 104).
Similarly, pixel buffer objects (PBOs) should be used to manage your image data. See “Best Practices for
Working with Texture Data” (page 118)
● Use framebuffer objects (FBOs) when you need to render to offscreen memory.
Framebuffer objects allow your application to create offscreen rendering targets without many of the
limitations of platform-dependent interfaces. See “Rendering to a Framebuffer Object” (page 53).
● Generate objects before binding them.
Earlier version of OpenGL allowed your applications to create its own object names before binding them.
However, you should avoid this. Always use the OpenGL API to generate object names.
● Migrate your OpenGL Applications to OpenGL 3.2
OpenGL on the Mac Platform
Making Great OpenGL Applications on the Macintosh
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
33The OpenGL 3.2 Core profile provides a clean break from earlier versions of OpenGL in favor of a simpler
shader-based pipeline. For better compatibility with future hardware and OS X releases, migrate your
applications away from legacy versions of OpenGL. Many of the recommendationslisted above are required
when your application uses OpenGL 3.2.
● Harness the power of Apple’s development tools.
Apple provides many toolsthat help create OpenGL applications and analyze and tune their performance.
Learning how to use these tools helps you create fast, reliable applications. “Tuning Your OpenGL
Application” (page 155) describes many of these tools.
OpenGL on the Mac Platform
Making Great OpenGL Applications on the Macintosh
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
34The OpenGL programming interface provides hundreds of drawing commands that drive graphics hardware.
It doesn't provide any commands that interface with the windowing system of an operating system. Without
a windowing system, the 3D graphics of an OpenGL program are trapped inside the GPU. Figure 2-1 shows a
cube drawn to a Cocoa view.
Figure 2-1 OpenGL content in a Cocoa view
This chapter shows how to display OpenGL drawing onscreen using the APIs provided by OS X. (This chapter
does not show how to use GLUT.) The first section describes the overall approach to drawing onscreen and
provides an overview of the functions and methods used by each API.
General Approach
To draw your content to a view or a layer, your application uses the NSOpenGL classes from within the Cocoa
application framework. While the CGL API is used by your applications only to create full-screen content, every
NSOpenGLContext object contains a CGL context object. This object can be retrieved from the
NSOpenGLContext when your application needs to reference it directly. To show the similarities between the
two, this chapter discusses both the NSOpenGL classes and the CGL API.
To draw OpenGL content to a window or view using the NSOpenGL classes, you need to perform these tasks:
1. Set up the renderer and buffer attributes that support the OpenGL drawing you want to perform.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
35
Drawing to a Window or ViewEach of the OpenGL APIs in OS X has its own set of constants that represent renderer and buffer attributes.
For example, the all-renderers attribute is represented by the NSOpenGLPFAAllRenderers constant in
Cocoa and the kCGLPFAAllRenderers constant in the CGL API.
2. Request, from the operating system, a pixel format object that encapsulates pixel storage information and
the renderer and buffer attributes required by your application. The returned pixel format object contains
all possible combinations of renderers and displays available on the system that your program runs on
and that meets the requirements specified by the attributes. The combinations are referred to as virtual
screens. (See “Virtual Screens” (page 26).)
There may be situationsfor which you want to ensure that your program uses a specific renderer. “Choosing
Renderer and Buffer Attributes” (page 64) discusses how to set up an attributes array that guarantees the
system passes back a pixel format object that uses only that renderer.
If an error occurs, your application may receive a NULL pixel format object. Your application must handle
this condition.
3. Create a rendering context and bind the pixel format object to it. The rendering context keeps track of
state information that controls such things as drawing color, view and projection matrices, characteristics
of light, and conventions used to pack pixels.
Your application needs a pixel format object to create a rendering context.
4. Release the pixel format object. Once the pixel format object is bound to a rendering context, itsresources
are no longer needed.
5. Bind a drawable object to the rendering context. For a windowed context, this is typically a Cocoa view.
6. Make the rendering context the current context. The system sends OpenGL drawing to whichever rendering
context is designated as the current one. It's possible for you to set up more than one rendering context,
so you need to make sure that the one you want to draw to is the current one.
7. Perform your drawing.
The specific functions or methods that you use to perform each of the steps are discussed in the sections that
follow.
Drawing to a Cocoa View
There are two ways to draw OpenGL content to a Cocoa view. If your application has modest drawing
requirements, then you can use the NSOpenGLView class. See “Drawing to an NSOpenGLView Class: A
Tutorial” (page 37).
Drawing to a Window or View
Drawing to a Cocoa View
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
36If your application is more complex and needs to support drawing to multiple rendering contexts, you may
want to consider subclassing the NSView class. For example, if your application supports drawing to multiple
views at the same time, you need to set up a custom NSView class. See “Drawing OpenGL Content to a Custom
View” (page 40).
Drawing to an NSOpenGLView Class: A Tutorial
The NSOpenGLView class is a lightweight subclass of the NSView class that provides convenience methods
for setting up OpenGL drawing. An NSOpenGLView object maintains an NSOpenGLPixelFormat object and
an NSOpenGLContext object into which OpenGL calls can be rendered. It provides methods for accessing
and managing the pixel format object and the rendering context, and handles notification of visible region
changes.
An NSOpenGLView object does notsupportsubviews. You can, however, divide the view into multiple rendering
areas using the OpenGL function glViewport.
This section provides step-by-step instructions for creating a simple Cocoa application that draws OpenGL
content to a view. The tutorial assumes that you know how to use Xcode and Interface Builder. If you have
never created an application using the Xcode development environment, see Getting Started with Tools.
1. Create a Cocoa application project named Golden Triangle.
2. Add the OpenGL framework to your project.
3. Add a new file to your project using the Objective-C class template. Name the file MyOpenGLView.m and
create a header file for it.
4. Open the MyOpenGLView.h file and modify the file so that it looks like the code shown in Listing 2-1 to
declare the interface.
Listing 2-1 The interface for MyOpenGLView
#import
@interface MyOpenGLView : NSOpenGLView
{
}
- (void) drawRect: (NSRect) bounds;
@end
5. Save and close the MyOpenGLView.h file.
6. Open the MyOpenGLView.m file and include the gl.h file, as shown in Listing 2-2.
Drawing to a Window or View
Drawing to a Cocoa View
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
37Listing 2-2 Include OpenGL/gl.h
#import "MyOpenGLView.h"
#include
@implementation MyOpenGLView
@end
7. Implement the drawRect: method asshown in Listing 2-3, adding the code after the @implementation
statement. The method sets the clear color to black and clears the color buffer in preparation for drawing.
Then, drawRect: calls your drawing routine, which you’ll add next. The OpenGL command glFlush
draws the content provided by your routine to the view.
Listing 2-3 The drawRect: method for MyOpenGLView
-(void) drawRect: (NSRect) bounds
{
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
drawAnObject();
glFlush();
}
8. Add the code to perform your drawing. In your own application, you'd perform whatever drawing is
appropriate. But for the purpose of learning how to draw OpenGL content to a view, add the code shown
in Listing 2-4. This code draws a 2D, gold-colored triangle, whose dimensions are not quite the dimensions
of a true golden triangle, but good enough to show how to perform OpenGL drawing.
Make sure that you insert this routine before the drawRect: method in the MyOpenGLView.m file.
Listing 2-4 Code that draws a triangle using OpenGL commands
static void drawAnObject ()
{
glColor3f(1.0f, 0.85f, 0.35f);
glBegin(GL_TRIANGLES);
{
Drawing to a Window or View
Drawing to a Cocoa View
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
38glVertex3f( 0.0, 0.6, 0.0);
glVertex3f( -0.2, -0.3, 0.0);
glVertex3f( 0.2, -0.3 ,0.0);
}
glEnd();
}
9. Open the MainMenu.xib in Interface Builder.
10. Change the window’s title to Golden Triangle.
11. Drag an NSOpenGLView object from the Library to the window. Resize the view to fit the window.
12. Change the class of this object to MyOpenGLView.
13. Open the Attributes pane of the inspector for the view, and take a look at the renderer and buffer attributes
that are available to set. These settings save you from setting attributes programmatically.
Only those attributes listed in the Interface Builder inspector are set when the view is instantiated. If you
need additional attributes, you need to set them programmatically.
14. Build and run your application. You should see content similar to the triangle shown in Figure 2-2.
Figure 2-2 The output from the Golden Triangle program
This example is extremely simple. In a more complex application, you'd want to do the following:
● Replace the immediate-mode drawing commands with commands that persist your vertex data inside
OpenGL. See “OpenGL Application Design Strategies” (page 89).
Drawing to a Window or View
Drawing to a Cocoa View
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
39●
In the interface for the view, declare a variable that indicates whether the view is ready to accept drawing.
A view is ready for drawing only if it is bound to a rendering context and that context is set to be the
current one.
● Cocoa does not call initialization routines for objects created in Interface Builder. If you need to perform
any initialization tasks, do so in the awakeFromNib method for the view. Note that because you set
attributes in the inspector, there is no need to set them up programmatically unless you need additional
ones. There is also no need to create a pixel format object programmatically; it is created and loaded when
Cocoa loads the nib file.
● Your drawRect: method should test whether the view is ready to draw into. You need to provide code
that handles the case when the view is not ready to draw into.
● OpenGL is at its best when doing real-time and interactive graphics. Your application needs to provide a
timer or support user interaction. For more information about creating animation in your OpenGL
application, see “Synchronize with the Screen Refresh Rate” (page 96).
Drawing OpenGL Content to a Custom View
This section provides an overview of the key tasks you need to perform to customize the NSView class for
OpenGL drawing. Before you create a custom view for OpenGL drawing, you should read “Creating a Custom
View” in View Programming Guide .
When you subclass the NSView class to create a custom view for OpenGL drawing, you override any Quartz
drawing or other content that is in that view. To set up a custom view for OpenGL drawing, subclass NSView
and create two private variables—one which is an NSOpenGLContext object and the other an
NSOpenGLPixelFormat object, as shown in Listing 2-5.
Listing 2-5 The interface for a custom OpenGL view
@class NSOpenGLContext, NSOpenGLPixelFormat;
@interface CustomOpenGLView : NSView
{
@private
NSOpenGLContext* _openGLContext;
NSOpenGLPixelFormat* _pixelFormat;
}
+ (NSOpenGLPixelFormat*)defaultPixelFormat;
- (id)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat*)format;
- (void)setOpenGLContext:(NSOpenGLContext*)context;
Drawing to a Window or View
Drawing to a Cocoa View
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
40- (NSOpenGLContext*)openGLContext;
- (void)clearGLContext;
- (void)prepareOpenGL;
- (void)update;
- (void)setPixelFormat:(NSOpenGLPixelFormat*)pixelFormat;
- (NSOpenGLPixelFormat*)pixelFormat;
@end
In addition to the usual methods for the private variables (openGLContext, setOpenGLContext:,
pixelFormat, and setPixelFormat:) you need to implement the following methods:
● + (NSOpenGLPixelFormat*) defaultPixelFormat
Use this method to allocate and initialize the NSOpenGLPixelFormat object.
● - (void) clearGLContext
Use this method to clear and release the NSOpenGLContext object.
● - (void) prepareOpenGL
Use this method to initialize the OpenGL state after creating the NSOpenGLContext object.
You need to override the update and initWithFrame: methods of the NSView class.
● update calls the update method of the NSOpenGLContext class.
● initWithFrame:pixelFormat retains the pixel format and sets up the notification
NSViewGlobalFrameDidChangeNotification. See Listing 2-6.
Listing 2-6 The initWithFrame:pixelFormat: method
- (id)initWithFrame:(NSRect)frameRect pixelFormat:(NSOpenGLPixelFormat*)format
{
self = [super initWithFrame:frameRect];
if (self != nil) {
_pixelFormat = [format retain];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_surfaceNeedsUpdate:)
name:NSViewGlobalFrameDidChangeNotification
object:self];
}
Drawing to a Window or View
Drawing to a Cocoa View
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
41return self;
}
- (void) _surfaceNeedsUpdate:(NSNotification*)notification
{
[self update];
}
If the custom view is not guaranteed to be in a window, you must also override the lockFocus method of
the NSView class. See Listing 2-7. This method makes sure that the view is locked prior to drawing and that
the context is the current one.
Listing 2-7 The lockFocus method
- (void)lockFocus
{
NSOpenGLContext* context = [self openGLContext];
[super lockFocus];
if ([context view] != self) {
[context setView:self];
}
[context makeCurrentContext];
}
The reshape method is not supported by the NSView class. You need to update bounds in the drawRect:
method, which should take the form shown in Listing 2-8.
Listing 2-8 The drawRect method for a custom view
-(void) drawRect
{
[context makeCurrentContext];
//Perform drawing here
[context flushBuffer];
}
Drawing to a Window or View
Drawing to a Cocoa View
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
42There may be other methods that you want to add. For example, you might consider detaching the context
from the drawable object when the custom view is moved from the window, as shown in Listing 2-9.
Listing 2-9 Detaching the context from a drawable object
-(void) viewDidMoveToWindow
{
[super viewDidMoveToWindow];
if ([self window] == nil)
[context clearDrawable];
}
Drawing to a Window or View
Drawing to a Cocoa View
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
43OpenGL is a pixel-based API so the NSOpenGLView class does not provide high-resolution surfaces by default.
Because adding more pixelsto renderbuffers has performance implications, you must explicitly opt in to support
high-resolution screens. It’s easy to enable high-resolution backing for an OpenGL view. When you do, you’ll
want to perform a few additional tasks to ensure the best possible high-resolution experience for your users.
Enable High-Resolution Backing for an OpenGL View
You can opt in to high resolution by calling the method setWantsBestResolutionOpenGLSurface: when
you initialize the view, and supplying YES as an argument:
[self setWantsBestResolutionOpenGLSurface:YES];
If you don’t opt in, the system magnifies the rendered results.
The wantsBestResolutionOpenGLSurface property is relevant only for views to which an
NSOpenGLContext object is bound. Its value does not affect the behavior of other views. For compatibility,
wantsBestResolutionOpenGLSurface defaultsto NO, providing a 1-pixel-per-point framebuffer regardless
of the backing scale factor for the display the view occupies. Setting this property to YES for a given view
causes AppKit to allocate a higher-resolution framebuffer when appropriate for the backing scale factor and
target display.
To function correctly with wantsBestResolutionOpenGLSurface set to YES, a view must perform correct
conversions between view units (points) and pixel units as needed. For example, the common practice of
passing the width and height of [self bounds] to glViewport() will yield incorrect results at high resolution,
because the parameters passed to the glViewport() function must be in pixels. As a result, you’ll get only
partial instead of complete coverage of the render surface. Instead, use the backing store bounds:
[self convertRectToBacking:[self bounds]];
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
44
Optimizing OpenGL for High ResolutionYou can also opt in to high resolution by enabling the Supports Hi-Res Backing setting for the OpenGL view
in Xcode, as shown in Figure 3-1.
Figure 3-1 Enabling high-resolution backing for an OpenGL view
Set Up the Viewport to Support High Resolution
The viewport dimensions are in pixelsrelative to the OpenGL surface. Passthe width and height to glViewPort
and use 0,0 for the x and y offsets. Listing 3-1 shows how to get the view dimensions in pixels and take the
backing store size into account.
Listing 3-1 Setting up the viewport for drawing
- (void)drawRect:(NSRect)rect // NSOpenGLView subclass
{
// Get view dimensions in pixels
NSRect backingBounds = [self convertRectToBacking:[self bounds]];
Optimizing OpenGL for High Resolution
Set Up the Viewport to Support High Resolution
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
45GLsizei backingPixelWidth = (GLsizei)(backingBounds.size.width),
backingPixelHeight = (GLsizei)(backingBounds.size.height);
// Set viewport
glViewport(0, 0, backingPixelWidth, backingPixelHeight);
// draw…
}
You don’t need to perform rendering in pixels, but you do need to be aware of the coordinate system you
want to render in. For example, if you want to render in points, this code will work:
glOrtho(NSWidth(bounds), NSHeight(bounds),...)
Adjust Model and Texture Assets
If you opt in to high-resolution drawing, you also need to adjust the model and texture assets of your app. For
example, when running on a high-resolution display, you might want to choose larger models and more
detailed textures to take advantage of the increased number of pixels. Conversely, on a standard-resolution
display, you can continue to use smaller models and textures.
If you create and cache textures when you initialize your app, you might want to consider a strategy that
accommodates changing the texture based on the resolution of the display.
Check for Calls Defined in Pixel Dimensions
These functions use pixel dimensions:
● glViewport (GLint x, GLint y, GLsizei width, GLsizei height)
● glScissor (GLint x, GLint y, GLsizei width, GLsizei height)
● glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, ...)
● glLineWidth (GLfloat width)
● glRenderbufferStorage (..., GLsizei width, GLsizei height)
● glTexImage2D (..., GLsizei width, GLsizei height, ...)
Optimizing OpenGL for High Resolution
Adjust Model and Texture Assets
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
46Tune OpenGL Performance for High Resolution
Performance is an important factor when determining whether to support high-resolution content. The
quadrupling of pixels that occurs when you opt in to high resolution requires more work by the fragment
processor. If your app performs many per-fragment calculations, the increase in pixels might reduce its frame
rate. If your app runs significantly slower at high resolution, consider the following options:
● Optimize fragment shader performance. (See “Tuning Your OpenGL Application” (page 155).)
● Choose a simpler algorithm to implement in your fragment shader. This reduces the quality of each
individual pixel to allow for rendering the overall image at a higher resolution.
● Use a fractional scale factor between 1.0 and 2.0. A scale factor of 1.5 provides better quality than a scale
factor of 1.0, but it needs to fill fewer pixels than an image scaled to 2.0.
● Multisampling antialiasing can be costly with marginal benefit at high resolution. If you are using it, you
might want to reconsider.
The best solution depends on the needs of your OpenGL app; you should test more than one of these options
and choose the approach that provides the best balance between performance and image quality.
Optimizing OpenGL for High Resolution
Tune OpenGL Performance for High Resolution
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
47Use a Layer-Backed View to Overlay Text on OpenGL Content
When you draw standard controls and Cocoa text to a layer-backed view, the system handles scaling the
contents of that layer for you. You need to perform only a few steps to set and use the layer. Compare the
controls and text in standard and high resolutions, as shown in Figure 3-2. The text looks the same on both
without any additional work on your part.
Figure 3-2 A text overlay scales automatically for standard resolution (left) and high resolution (right)
To set up a layer-backed view for OpenGL content
1. Set the wantsLayer property of your NSOpenGLView subclass to YES.
Enabling the wantsLayer property of an NSOpenGLView object activates layer-backed rendering of
the OpenGL view. Drawing a layer-backed OpenGL view proceeds mostly normally through the view’s
drawRect: method. The layer-backed rendering mode usesits own NSOpenGLContext object, which
is distinct from the NSOpenGLContext that the view uses for drawing in non-layer-backed mode.
AppKit automatically creates this context and assigns it to the view by invoking the
setOpenGLContext: method. The view’s openGLContext accessor will return the layer-backed
OpenGL context (rather than the non-layer-backed context) while the view is operating in layer-backed
mode.
Optimizing OpenGL for High Resolution
Use a Layer-Backed View to Overlay Text on OpenGL Content
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
482. Create the layer content either as a XIB file or programmatically.
The controls shown in Figure 3-2 were created in a XIB file by subclassing NSBox and using static text
with a variety of standard controls. Using this approach allows the NSBox subclass to ignore mouse
events while still allowing the user to interact with the OpenGL content.
3. Add the layer to the OpenGL view by calling the addSublayer: method.
Use an Application Window for Fullscreen Operation
For the best user experience, if you want your app to run full screen, create a window that covers the entire
screen. This approach offers two advantages:
● The system provides optimized context performance.
● Users will be able to see critical system dialogs above your content.
You should avoid changing the display mode of the system.
Convert the Coordinate Space When Hit Testing
Always convert window event coordinates when performing hit testing in OpenGL. The locationInWindow
method of the NSEvent class returns the receiver’s location in the base coordinate system of the window. You
then need to call the convertPoint:fromView: method to get the local coordinates for the OpenGL view.
NSPoint aPoint = [theEvent locationInWindow];
NSPoint localPoint = [myOpenGLView convertPoint:aPoint fromView:nil];
Optimizing OpenGL for High Resolution
Use an Application Window for Fullscreen Operation
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
49In OS X, you have the option to draw to the entire screen. This is a common scenario for games and other
immersive applications, and OS X applies additional optimizations to improve the performance of full-screen
contexts.
Figure 4-1 Drawing OpenGL content to the full screen
OS X v10.6 and later automatically optimize the performance ofscreen-sized windows, allowing your application
to take complete advantage of the window server environment on OS X. For example, critical operating system
dialogs may be displayed over your content when necessary.
For information about high-resolution and full-screen drawing, see “Use an Application Window for Fullscreen
Operation” (page 49).
Creating a Full-Screen Application
Creating a full-screen context is very simple. Your application should follow these steps:
1. Create a screen-sized window on the display you want to take over:
NSRect mainDisplayRect = [[NSScreen mainScreen] frame];
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
50
Drawing to the Full ScreenNSWindow *fullScreenWindow = [[NSWindow alloc] initWithContentRect:
mainDisplayRect styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered defer:YES];
2. Set the window level to be above the menu bar.:
[fullScreenWindow setLevel:NSMainMenuWindowLevel+1];
3. Perform any other window configuration you desire:
[fullScreenWindow setOpaque:YES];
[fullScreenWindow setHidesOnDeactivate:YES];
4. Create a view with a double-buffered OpenGL context and attach it to the window:
NSOpenGLPixelFormatAttribute attrs[] =
{
NSOpenGLPFADoubleBuffer,
0
};
NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc]
initWithAttributes:attrs];
NSRect viewRect = NSMakeRect(0.0, 0.0, mainDisplayRect.size.width,
mainDisplayRect.size.height);
MyOpenGLView *fullScreenView = [[MyOpenGLView alloc] initWithFrame:viewRect
pixelFormat: pixelFormat];
[fullScreenWindow setContentView: fullScreenView];
5. Show the window:
[fullScreenWindow makeKeyAndOrderFront:self];
That’s all you need to do. Your content is in a window that is above most other content, but because it is in a
window, OS X can still show critical UI elements above your content when necessary (such as error dialogs).
When there is no content above your full-screen window, OS X automatically attemptsto optimize this context’s
performance. For example, when your application calls flushBuffer on the NSOpenGLContext object, the
Drawing to the Full Screen
Creating a Full-Screen Application
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
51system may swap the buffers rather than copying the contents of the back buffer to the front buffer. These
performance optimizations are not applied when your application adds the NSOpenGLPFABackingStore
attribute to the context. Because the system may choose to swap the buffers rather than copy them, your
application must completely redraw the scene after every call to flushBuffer. For more information on
NSOpenGLPFABackingStore, see “Ensuring That Back Buffer Contents Remain the Same” (page 66).
Avoid changing the display resolution from that chosen by the user. If your application needs to render data
at a lower resolution for performance reasons, you can explicitly create a back buffer at the desired resolution
and allow OpenGL to scale those results to the display. See “Controlling the Back Buffer Size” (page 78).
Drawing to the Full Screen
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
52OpenGL applications may want to use OpenGL to render images without actually displaying them to the user.
For example, an image processing application might render the image, then copy that image back to the
application and save it to disk. Another useful strategy is to create intermediate images that are used later to
render additional content. For example, your application might want to render an image and use it as a texture
in a future rendering pass. For best performance, offscreen targets should be managed by OpenGL. Having
OpenGL manage offscreen targets allows you to avoid copying pixel data back to your application, except
when this is absolutely necessary.
OS X offers two useful options for creating offscreen rendering targets:
● Framebuffer objects. The OpenGL framebuffer extension allows your application to create fully supported
offscreen OpenGL framebuffers. Framebuffer objects are fully supported as a cross-platform extension, so
they are the preferred way to create offscreen rendering targets. See “Rendering to a Framebuffer
Object” (page 53).
● Pixel buffer drawable objects. Pixel buffer drawable objects are an Apple-specific technology for creating
an offscreen target. Each of the Apple-specific OpenGL APIs provides routines to create an offscreen
hardware accelerated pixel buffer. Pixel buffers are recommended for use only when framebuffer objects
are not available. See “Rendering to a Pixel Buffer” (page 60).
Rendering to a Framebuffer Object
The OpenGL framebuffer extension (GL_EXT_framebuffer_object) allows applications to create offscreen
rendering targets from within OpenGL. OpenGL manages the memory for these framebuffers.
Note: Extensions are available on a per-renderer basis. Before you use framebuffer objects you must
check each renderer to make sure that itsupportsthe extension. See “Detecting Functionality” (page
83) for more information.
A framebuffer object(FBO) issimilar to a drawable object, except a drawable object is a window-system-specific
object, whereas a framebuffer object is a window-agnostic object that's defined in the OpenGL standard. After
drawing to a framebuffer object, it is straightforward to read the pixel data to the application, or to use it as
source data for other OpenGL commands.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
53
Drawing OffscreenFramebuffer objects offer a number of benefits:
● They are window-system independent, which makes porting code easier.
● They are easy to set up and save memory. There is no need to set up attributes and obtain a pixel format
object.
● They are associated with a single OpenGL context, whereas each pixel buffer must be bound to a context.
● You can switch between them faster since there is no context switch as with pixel buffers. Because all
commands are rendered by a single context, no additional serialization is required.
● They can share depth buffers; pixel buffers cannot.
● You can use them for 2D pixel images and texture images.
Completeness is a key concept to understanding framebuffer objects. Completeness is a state that indicates
whether a framebuffer object meets all the requirements for drawing. You test for this state after performing
all the necessary setup work. If a framebuffer object is not complete, it cannot be used as the destination for
rendering operations and as a source for read operations.
Completeness is dependent on many factors that are not possible to condense into one or two statements,
but these factors are thoroughly defined in the OpenGL specification for the framebuffer object extension. The
specification describes the requirements for internal formats of images attached to the framebuffer, how to
determine if a format is color-, depth-, and stencil-renderable, as well as other requirements.
Prior to using framebuffer objects, read the OpenGL specification, which not only defines the framebuffer
object API, but provides detailed definitions of all the terms necessary to understand their use and shows
several code examples.
The remainder of thissection provides an overview of how to use a framebuffer as either a texture or an image.
The functions used to set up textures and images are slightly different. The API for images usesthe renderbuffer
terminology defined in the OpenGL specification. A renderbuffer image is simply a 2D pixel image. The API
for textures uses texture terminology, as you might expect. For example, one of the calls for setting up a
framebuffer object for a texture is glFramebufferTexture2DEXT, whereasthe call forsetting up a framebuffer
object for an image is glFramebufferRenderbufferEXT. You'll see how to set up a simple framebuffer
object for each type of drawing, starting first with textures.
Using a Framebuffer Object as a Texture
These are the basic steps needed to set up a framebuffer object for drawing a texture offscreen:
1. Make sure the framebuffer extension (GL_EXT_framebuffer_object) is supported on the system that
your code runs on. See “Determining the OpenGL Capabilities Supported by the Renderer” (page 83).
Drawing Offscreen
Rendering to a Framebuffer Object
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
542. Check the renderer limits. For example, you might want to call the OpenGL function glGetIntegerv to
check the maximum texture size (GL_MAX_TEXTURE_SIZE) or find out the maximum number of color
buffers you can attach to the framebuffer object(GL_MAX_COLOR_ATTACHMENTS_EXT).
3. Generate a framebuffer object name by calling the following function:
void glGenFramebuffersEXT (GLsizei n, GLuint *ids);
n is the number of framebuffer object names that you want to create.
On return, *ids points to the generated names.
4. Bind the framebuffer object name to a framebuffer target by calling the following function:
void glBindFramebufferEXT(GLenum target, GLuint framebuffer);
target should be the constant GL_FRAMEBUFFER_EXT.
framebuffer is set to an unused framebuffer object name.
On return, the framebuffer object is initialized to the state values described in the OpenGL specification
for the framebuffer object extension. Each attachment point of the framebuffer is initialized to the
attachment point state values described in the specification. The number of attachment points is equal
to GL_MAX_COLOR_ATTACHMENTS_EXT plus 2 (for depth and stencil attachment points).
Whenever a framebuffer object is bound, drawing commands are directed to it instead of being directed
to the drawable associated with the rendering context.
5. Generate a texture name.
void glGenTextures(GLsizei n, GLuint *textures);
n is the number of texture object names that you want to create.
On return, *textures points to the generated names.
6. Bind the texture name to a texture target.
void glBindTexture(GLenum target, GLuint texture);
target is the type of texture to bind.
texture is the texture name you just created.
7. Set up the texture environment and parameters.
Drawing Offscreen
Rendering to a Framebuffer Object
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
558. Define the texture by calling the appropriate OpenGL function to specify the target, level of detail, internal
format, dimensions, border, pixel data format, and texture data storage.
9. Attach the texture to the framebuffer by calling the following function:
void glFramebufferTexture2DEXT (GLenum target, GLenum attachment,
GLenum textarget, GLuint texture,
GLint level);
target must be GL_FRAMEBUFFER_EXT.
attachment must be one of the attachment points of the framebuffer: GL_STENCIL_ATTACHMENT_EXT,
GL_DEPTH_ATTACHMENT_EXT, or GL_COLOR_ATTACHMENTn_EXT, where n is a number from 0 to
GL_MAX_COLOR_ATTACHMENTS_EXT-1.
textarget is the texture target.
texture is an existing texture object.
level is the mipmap level of the texture image to attach to the framebuffer.
10. Check to make sure that the framebuffer is complete by calling the following function:
GLenum glCheckFramebufferStatusEXT(GLenum target);
target must be the constant GL_FRAMEBUFFER_EXT.
This function returns a status constant. You must test to make sure that the constant is
GL_FRAMEBUFFER_COMPLETE_EXT. If it isn't, see the OpenGL specification for the framebuffer object
extension for a description of the other constants in the status enumeration.
11. Render content to the texture. You must make sure to bind a different texture to the framebuffer object
or disable texturing before you render content. If you render to a framebuffer object texture attachment
with that same texture currently bound and enabled, the result is undefined.
12. To draw the contents of the texture to a window, make the window the target of all rendering commands
by calling the function glBindFramebufferEXT and passing the constant GL_FRAMEBUFFER_EXT and
0. The window is always specified as 0.
13. Use the texture attachment as a normal texture by binding it, enabling texturing, and drawing.
14. Delete the texture.
15. Delete the framebuffer object by calling the following function:
void glDeleteFramebuffersEXT (GLsizei n, const GLuint *framebuffers);
Drawing Offscreen
Rendering to a Framebuffer Object
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
56n is the number of framebuffer objects to delete.
*framebuffers points to an array that contains the framebuffer object names.
Listing 5-1 shows code that performs these tasks. This example creates and draws to a single framebuffer
object.
Listing 5-1 Setting up a framebuffer for texturing
GLuint framebuffer, texture;
GLenum status;
glGenFramebuffersEXT(1, &framebuffer);
// Set up the FBO with one texture attachment
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer);
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, TEXWIDE, TEXHIGH, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_2D, texture, 0);
status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
// Handle error here
// Your code to draw content to the FBO
// ...
// Make the window the target
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
//Your code to use the contents of the FBO
// ...
//Tear down the FBO and texture attachment
glDeleteTextures(1, &texture);
glDeleteFramebuffersEXT(1, &framebuffer);
Drawing Offscreen
Rendering to a Framebuffer Object
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
57Using a Framebuffer Object as an Image
There is a lot of similarity between setting up a framebuffer object for drawing images and setting one up to
draw textures. These are the basic steps needed to set up a framebuffer object for drawing a 2D pixel image
(a renderbuffer image) offscreen:
1. Make sure the framebuffer extension (EXT_framebuffer_object) is supported on the renderer that
your code runs on.
2. Check the renderer limits. For example, you might want to call the OpenGL function glGetIntegerv to
find out the maximum number of color buffers (GL_MAX_COLOR_ATTACHMENTS_EXT).
3. Generate a framebuffer object name by calling the function glGenFramebuffersEXT.
4. Bind the framebuffer object name to a framebuffer target by calling the function glBindFramebufferEXT.
5. Generate a renderbuffer object name by calling the following function:
void glGenRenderbuffersEXT (GLsizei n, GLuint *renderbuffers );
n is the number of renderbuffer object names to create.
*renderbuffers points to storage for the generated names.
6. Bind the renderbuffer object name to a renderbuffer target by calling the following function:
void glBindRenderbufferEXT (GLenum target, GLuint renderbuffer);
target must be the constant GL_RENDERBUFFER_EXT.
renderbuffer is the renderbuffer object name generated previously.
7. Create data storage and establish the pixel format and dimensions of the renderbuffer image by calling
the following function:
void glRenderbufferStorageEXT (GLenum target, GLenum internalformat,
GLsizei width, GLsizei height);
target must be the constant GL_RENDERBUFFER_EXT.
internalformat is the pixel format of the image. The value must be RGB, RGBA, DEPTH_COMPONENT,
STENCIL_INDEX, or one of the other formats listed in the OpenGL specification.
width is the width of the image, in pixels.
height is the height of the image, in pixels.
Drawing Offscreen
Rendering to a Framebuffer Object
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
588. Attach the renderbufferto a framebuffertarget by calling the function glFramebufferRenderbufferEXT.
void glFramebufferRenderbufferEXT(GLenum target, GLenum attachment,
GLenum renderbuffertarget, GLuint
renderbuffer);
target must be the constant GL_FRAMEBUFFER_EXT.
attachment should be one of the attachment points of the framebuffer: GL_STENCIL_ATTACHMENT_EXT,
GL_DEPTH_ATTACHMENT_EXT, or GL_COLOR_ATTACHMENTn_EXT, where n is a number from 0 to
GL_MAX_COLOR_ATTACHMENTS_EXT–1.
renderbuffertarget must be the constant GL_RENDERBUFFER_EXT.
renderbuffer should be set to the name of the renderbuffer object that you want to attach to the
framebuffer.
9. Check to make sure that the framebuffer is complete by calling the following function:
enum glCheckFramebufferStatusEXT(GLenum target);
target must be the constant GL_FRAMEBUFFER_EXT.
This function returns a status constant. You must test to make sure that the constant is
GL_FRAMEBUFFER_COMPLETE_EXT. If it isn't, see the OpenGL specification for the framebuffer object
extension for a description of the other constants in the status enumeration.
10. Render content to the renderbuffer.
11. To access the contents of the renderbuffer object, bind the framebuffer object and then use OpenGL
functions such as glReadPixels or glCopyTexImage2D.
12. Delete the framebuffer object with its renderbuffer attachment.
Listing 5-2 shows code that sets up and draws to a single renderbuffer object. Your application can set up
more than one renderbuffer object if it requires them.
Listing 5-2 Setting up a renderbuffer for drawing images
GLuint framebuffer, renderbuffer;
GLenum status;
// Set the width and height appropriately for your image
GLuint imageWidth = 1024,
imageHeight = 1024;
Drawing Offscreen
Rendering to a Framebuffer Object
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
59//Set up a FBO with one renderbuffer attachment
glGenFramebuffersEXT(1, &framebuffer);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer);
glGenRenderbuffersEXT(1, &renderbuffer);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, renderbuffer);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGBA8, imageWidth, imageHeight);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
GL_RENDERBUFFER_EXT, renderbuffer);
status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
// Handle errors
//Your code to draw content to the renderbuffer
// ...
//Your code to use the contents
// ...
// Make the window the target
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
// Delete the renderbuffer attachment
glDeleteRenderbuffersEXT(1, &renderbuffer);
Rendering to a Pixel Buffer
The OpenGL extension string GL_APPLE_pixel_buffer provides hardware-accelerated offscreen rendering
to a pixel buffer. A pixel buffer is typically used as a texture source. It can also be used for remote rendering.
Important: Pixel buffers are deprecated starting with OS X v10.7 and are not supported by the OpenGL
3.2 Core profile; use framebuffer objects instead.
You must create a rendering context for each pixel buffer. For example, if you want to use a pixel buffer as a
texture source, you create one rendering context attached to the pixel buffer and a second context attached
to a window or view.
The first step in using a pixel buffer is to create it. The Apple-specific OpenGL APIs each provide a routine for
this purpose:
Drawing Offscreen
Rendering to a Pixel Buffer
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
60● The NSOpenGLPixelBuffer method
initWithTextureTarget:textureInternalFormat:textureMaxMipMapLevel:pixelsWide:pixelsHigh:
● The CGL function CGLCreatePBuffer
Each of these routinesrequiresthat you provide a texture target, an internal format, a maximum mipmap level,
and the width and height of the texture.
The texture target must be one of these OpenGL texture constants: GL_TEXTURE_2D for a 2D texture,
GL_TEXTURE_RECTANGLE_ARB for a rectangular (not power-of-two) texture, or GL_TEXTURE_CUBE_MAP for
a cube map texture.
The internal format specifies how to interpret the data for texturing operations. You can supply any of these
options: GL_RGB (each pixel is a three-component group), GL_RGBA (each pixel is a four-component group),
or GL_DEPTH_COMPONENT (each pixel is a single depth component).
The maximum mipmap level should be 0 for a pixel buffer that does not have a mipmap. The value that you
supply should not exceed the actual maximum number of mipmap levels that can be represented with the
given width and height.
Note that none of the routines that create a pixel buffer allocate the storage needed. The storage is allocated
by the system at the time that you attach the pixel buffer to a rendering context.
Setting Up a Pixel Buffer for Offscreen Drawing
After you create a pixel buffer, the general procedure for using a pixel buffer for drawing is similar to the way
you set up windows and views for drawing:
1. Specify renderer and buffer attributes.
2. Obtain a pixel format object.
3. Create a rendering context and make it current.
4. Attach a pixel buffer to the context using the appropriate Apple OpenGL attachment function:
● The setPixelBuffer:cubeMapFace:mipMapLevel:currentVirtualScreen: method of the
NSOpenGLContext class instructs the receiver to render into a pixel buffer.
● The CGL function CGLSetPBuffer attaches a CGL rendering context to a pixel buffer.
5. Draw, as you normally would, using OpenGL.
Using a Pixel Buffer as a Texture Source
Pixel bufferslet you perform direct texturing without incurring the cost of extra copies. After drawing to a pixel
buffer, you can create a texture by following these steps:
Drawing Offscreen
Rendering to a Pixel Buffer
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
611. Generate a texture name by calling the OpenGL function glGenTextures.
2. Bind the named texture to a target by calling the OpenGL function glBindTexture.
3. Set the texture parameters by calling OpenGL function glTexEnvParameter.
4. Set up the pixel buffer asthe source for the texture by calling one of the following Apple OpenGL functions:
● The setTextureImageToPixelBuffer:colorBuffer: method of the NSOpenGLContext class
attaches the image data in the pixel buffer to the texture object currently bound by the receiver.
● The CGL function CGLTexImagePBuffer binds the contents of a CGL pixel buffer as the data source
for a texture object.
The context that you attach to the pixel buffer is the target rendering context: the context that uses the
pixel buffer as the source of the texture data. Each of these routines requires a source parameter, which
is an OpenGL constant that specifies the source buffer to texture from. The source parameter must be a
valid OpenGL buffer, such as GL_FRONT, GL_BACK, or GL_AUX0, and should be compatible with the buffer
attributes used to create the OpenGL context associated with the pixel buffer. This means that the pixel
buffer must possess the buffer in question for texturing to succeed. For example, if the buffer attribute
used with the pixel buffer is only single buffered, then texturing from the GL_BACK buffer will fail.
If you modify content of any pixel buffer that contains mipmap levels, you must call the appropriate Apple
OpenGL function again (setTextureImageToPixelBuffer:colorBuffer: or CGLTexImagePBuffer)
before drawing with the pixel buffer to ensure that the content issynchronized with OpenGL. To synchronize
the content of pixel buffers without mipmaps, simply rebind to the texture object using glBind.
5. Draw primitives using the appropriate texture coordinates. (See "The Red book"—OpenGL Programming
Guide—for details.)
6. Call glFlush to cause all drawing commands to be executed.
7. When you no longer need the texture object, call the OpenGL function glDeleteTextures.
8. Set the current context to NULL using one of the Apple OpenGL routines:
● The makeCurrentContext method of the NSOpenGLContext class
● The CGL function CGLSetCurrentContext
9. Destroy the pixel buffer by calling CGLDestroyPBuffer.
10. Destroy the context by calling CGLDestroyContext.
11. Destroy the pixel format by calling CGLDestroyPixelFormat.
You might find these guidelines useful when using pixel buffers for texturing:
Drawing Offscreen
Rendering to a Pixel Buffer
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
62● You cannot make OpenGL texturing calls that modify pixel buffer content (such as glTexSubImage2D or
glCopyTexImage2D) with the pixel buffer as the destination. You can use texturing commands to read
data from a pixel buffer, such as glCopyTexImage2D, with the pixel buffer texture as the source. You can
also use OpenGL functions such as glReadPixels to read the contents of a pixel buffer directly from the
pixel buffer context.
● Texturing can fail to produce the intended results without reporting an error. You must make sure that
you enable the proper texture target, set a compatible filter mode, and adhere to other requirements
described in the OpenGL specification.
● You are not required to set up contextsharing when you texture from a pixel buffer. You can have different
pixel format objects and rendering contexts for both the pixel buffer and the target drawable object,
without sharing resources, and still texture using a pixel buffer in the target context.
Rendering to a Pixel Buffer on a Remote System
Follow these steps to render to a pixel buffer on a remote system. The remote system does not need to have
a display attached to it.
1. When you set the renderer and buffer attributes, include the remote pixel buffer attribute
kCGLPFARemotePBuffer.
2. Log in to the remote machine using the ssh command to ensure security.
3. Run the application on the target system.
4. Retrieve the content.
Drawing Offscreen
Rendering to a Pixel Buffer
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
63Renderer and buffer attributes determine the renderers that the system chooses for your application. Each of
the Apple-specific OpenGL APIs provides constants that specify a variety of renderer and buffer attributes. You
supply a list of attribute constants to one of the Apple OpenGL functions for choosing a pixel format object.
The pixel format object maintains a list of renderers that meet the requirements defined by those attributes.
In a real-world application, selecting attributes is an art because you don't know the exact combination of
hardware and software that your application will run on. An attribute list that is too restrictive may miss out
on future capabilities or it may fail to return renderers on some systems. For example, if you specify a buffer
of a specific depth, your application won't be able to take advantage of a larger buffer when more memory is
available in the future. In this case, you might specify a required minimum and direct OpenGL to use the
maximum available.
Although you might specify attributes that make your OpenGL content look and run its best, you also need
to consider whether your application should run on a less-capable system with less speed or detail. If tradeoffs
are acceptable, you need to set the attributes accordingly.
OpenGL Profiles (OS X v10.7)
When your application is running on OS X v10.7, it should always include the kCGLPFAOpenGLProfile
attribute, followed by a constant for the profile whose functionality your application requires. A profile affects
different parts of OpenGL in OS X:
● A profile requires that a specific version of the OpenGL API must provided by the renderer. The renderer
may implement a different version of the OpenGL specification only if that version implements the same
functions and constants required by the profile; typically, this means a renderer that supports a later
version of the OpenGL specification that did not remove or alter behavior specified in the version of the
OpenGL specification your application requested.
● The profile alters the list of OpenGL extensions returned by the renderer. For example, extensions whose
functionality is provided by the version of the OpenGL specification you requested are not also returned
in the list of extensions.
● On OS X, the profile affects what other renderer and buffer attributes may be included in the attributes
list.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
64
Choosing Renderer and Buffer AttributesFollow these guidelines to choose an OpenGL profile:
●
If you are developing a new OS X v10.7 application, implement your OpenGL functionality using the
OpenGL 3.2 Core profile; include the kCGLOGLPVersion_3_2_Core constant.
The OpenGL 3.2 core profile is defined by Khronos and explicitly removes removes deprecated features
described in earlier versions of the OpenGL specification; further the core profile prohibits these functions
from being added back into OpenGL using extensions. OpenGL 3.2 core represents a complete break from
the fixed function pipeline of OpenGL 1.x in favor of a clean, lean shader-based pipeline.
When you use the OpenGL 3.2 Core profile on OS X, legacy extensions are removed wherever their
functionality is already provided by OpenGL 3.2. Further, pixel and buffer format attributesthat are marked
as deprecated may not be used in conjunction with the OpenGL 3.2 core profile.
●
If you are updating an existing OS X application, include the kCGLOGLPVersion_Legacy constant.
The legacy profile provides the same functionality found in earlier versions of OS X, with no changes. It
continues to support older extensions as well as deprecated pixel and buffer format attributes. No new
functionality will be added to the legacy profile in future versions of OS X.
●
If you want to use OpenGL 3.2 in your application, but also want to support earlier versions of OS X or
Macsthat lack hardware support for OpenGL 3.2, you must implement multiple OpenGL rendering options
in your application. On OS X v10.7, your application should first test to see if OpenGL 3.2 is supported. If
OpenGL 3.2 is supported, create a context and provide it to your OpenGL 3.2 rendering path. Otherwise,
search for a pixel format using the legacy profile instead.
For more information on migrating an application to OpenGL 3.2, see “Updating an Application to Support
the OpenGL 3.2 Core Specification” (page 168).
Buffer Size Attribute Selection Tips
Follow these guidelines to choose buffer attributes that specify buffer size:
● To choose color, depth, and accumulation buffers that are greater than or equal to a size you specify, use
the minimum policy attribute (NSOpenGLPFAMinimumPolicy or kCGLPFAMinimumPolicy).
● To choose color, depth, and accumulation buffers that are closest to a size you specify, use the closest
policy attribute (NSOpenGLPFAClosestPolicy or kCGLPFAClosestPolicy).
● To choose the largest color, depth, and accumulation buffers available, use the maximum policy attribute
(NSOpenGLPFAMaximumPolicy or kCGLPFAMaximumPolicy). Aslong as you pass a value that is greater
than 0, this attribute specifies the use of color, depth, and accumulation buffers that are the largest size
possible.
Choosing Renderer and Buffer Attributes
Buffer Size Attribute Selection Tips
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
65Ensuring That Back Buffer Contents Remain the Same
When your application uses a double-buffered context, it displays the rendered image by calling a function to
flush the image to the screen— theNSOpenGLContext class’s flushBuffer method or the CGL function
CGLFlushDrawable. When the image is displayed, the contents of the back buffer are not preserved. The
next time your application wants to update the back buffer, it must completely redraw the scene.
Your application can add a backing store attribute (NSOpenGLPFABackingStore or kCGLPFABackingStore)
to preserve the contents of the buffer after the back buffer is flushed. Adding this attribute disables some
optimizations that the system can perform, which may impact the performance of your application.
Ensuring a Valid Pixel Format Object
The pixel format routines (the initWithAttributes: method of the NSOpenGLPixelFormat class and the
CGLChoosePixelFormat function) return a pixel format object to your application that you use to create a
rendering context. The buffer and renderer attributes that you supply to the pixel format routine determine
the characteristics of the OpenGL drawing sent to the rendering context. If the system can't find at least one
pixel format that satisfies the constraints specified by the attribute array, it returns NULL for the pixel format
object. In this case, your application should have an alternative that ensures it can obtain a valid object.
One alternative is to set up your attribute array with the least restrictive attribute first and the most restrictive
attribute last. Then, it is fairly easy to adjust the attribute list and make another request for a pixel format
object. The code in Listing 6-1 illustrates this technique using the CGL API. Notice that the initial attributes list
is set up with the supersample attribute last in the list. If the function CGLChoosePixelFormat returns NULL,
it clears the supersample attribute to NULL and tries again.
Listing 6-1 Using the CGL API to create a pixel format object
int last_attribute = 6;
CGLPixelFormatAttribute attribs[] =
{
kCGLPFAAccelerated,
kCGLPFAColorSize, 24
kCGLPFADepthSize, 16,
kCGLPFADoubleBuffer,
kCGLPFASupersample,
0
};
Choosing Renderer and Buffer Attributes
Ensuring That Back Buffer Contents Remain the Same
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
66CGLPixelFormatObj pixelFormatObj;
GLint numPixelFormats;
long value;
CGLChoosePixelFormat (attribs, &pixelFormatObj, &numPixelFormats);
if( pixelFormatObj == NULL ) {
attribs[last_attribute] = NULL;
CGLChoosePixelFormat (attribs, &pixelFormatObj, &numPixelFormats);
}
if( pixelFormatObj == NULL ) {
// Your code to notify the user and take action.
}
Ensuring a Specific Type of Renderer
There are times when you want to ensure that you obtain a pixel format that supports a specific renderer type,
such as a hardware-accelerated renderer. Table 6-1 lists attributes that support specific types of renderers. The
table reflects the following tips for setting up pixel formats:
● To select only hardware-accelerated renderers, use both the accelerated and no-recovery attributes.
● To use only the floating-point software renderer, use the appropriate generic floating-point constant.
● To render to system memory, use the offscreen pixel attribute. Note that this rendering option does not
use hardware acceleration.
● To render offscreen with hardware acceleration, specify a pixel buffer attribute. (See “Rendering to a Pixel
Buffer” (page 60).)
Table 6-1 Renderer types and pixel format attributes
Renderer type CGL Cocoa
NSOpenGLPFAAccelerated
NSOpenGLPFANoRecovery
kCGLPFAAccelerated
kCGLPFANoRecovery
Hardware-accelerated
onscreen
Choosing Renderer and Buffer Attributes
Ensuring a Specific Type of Renderer
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
67Renderer type CGL Cocoa
NSOpenGLPFARendererID
kCGLRendererGenericFloatID
kCGLPFARendererID
kCGLRendererGenericFloatID
Software (floating-point)
System memory (not kCGLPFAOffScreen NSOpenGLPFAOffScreen
accelerated)
Hardware-accelerated kCGLPFAPBuffer NSOpenGLPFAPixelBuffer
offscreen
Ensuring a Single Renderer for a Display
In some cases you may want to use a specific hardware renderer and nothing else. Since the OpenGL framework
normally provides a software renderer as a fallback in addition to whatever hardware renderer it chooses, you
need to prevent OpenGL from choosing the software renderer as an option. To do this, specify the no-recovery
attribute for a windowed drawable object.
Limiting a context to use a specific display, and thus a single renderer, has its risks. If your application runs on
a system that uses more than one display, dragging a windowed drawable object from one display to the other
is likely to yield a less than satisfactory result. Either rendering fails, or OpenGL uses the specified renderer and
then copiesthe result to the second display. The same unsatisfactory result happens when attaching a full-screen
context to another display. If you choose to use the hardware renderer associated with a specific display, you
need to add code that detects and handles display changes.
The code examples that follow show how to use each of the Apple-specific OpenGL APIs to set up a context
that uses a single renderer. Listing 6-2 shows how to set up an NSOpenGLPixelFormat object that supports
a single renderer. The attribute NSOpenGLPFANoRecovery specifies to OpenGL not to provide the fallback
option of the software renderer.
Listing 6-2 Setting an NSOpenGLContext object to use a specific display
#import
+ (NSOpenGLPixelFormat*)defaultPixelFormat
{
NSOpenGLPixelFormatAttribute attributes [] = {
NSOpenGLPFAScreenMask, 0,
NSOpenGLPFANoRecovery,
Choosing Renderer and Buffer Attributes
Ensuring a Single Renderer for a Display
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
68NSOpenGLPFADoubleBuffer,
(NSOpenGLPixelFormatAttribute)nil };
CGDirectDisplayID display = CGMainDisplayID ();
// Adds the display mask attribute for selected display
attributes[1] = (NSOpenGLPixelFormatAttribute)
CGDisplayIDToOpenGLDisplayMask (display);
return [[(NSOpenGLPixelFormat *)[NSOpenGLPixelFormat alloc]
initWithAttributes:attributes]
autorelease];
}
Listing 6-3 shows how to use CGL to set up a context that uses a single renderer. The attribute
kCGLPFANoRecovery ensures that OpenGL does not provide the fallback option of the software renderer.
Listing 6-3 Setting a CGL context to use a specific display
#include
CGLPixelFormatAttribute attribs[] = { kCGLPFADisplayMask, 0,
kCGLPFANoRecovery,
kCGLPFADoubleBuffer,
0 };
CGLPixelFormatObj pixelFormat = NULL;
GLint numPixelFormats = 0;
CGLContextObj cglContext = NULL;
CGDirectDisplayID display = CGMainDisplayID ();
// Adds the display mask attribute for selected display
attribs[1] = CGDisplayIDToOpenGLDisplayMask (display);
CGLChoosePixelFormat (attribs, &pixelFormat, &numPixelFormats);
Allowing Offline Renderers
Adding the attribute NSOpenGLPFAAllowOfflineRenderers allows OpenGL to include offline renderers in
the list of virtual screens returned in the pixel format object. Apple recommends you include this attribute,
because it allows your application to work better in environments where renderers come and go,such as when
a new display is plugged into a Mac.
Choosing Renderer and Buffer Attributes
Allowing Offline Renderers
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
69If your application includes NSOpenGLPFAAllowOfflineRenderers in the list of attributes, your application
must also watch for display changes and update its rendering context. See “Update the Rendering Context
When the Renderer or Geometry Changes” (page 72).
OpenCL
If your applications uses OpenCL to perform other computations, you may want to find an OpenGL renderer
that also supports OpenCL. To do this, add the attribute NSOpenGLPFAAcceleratedCompute to the pixel
format attribute list. Adding this attribute restricts the list of renderers to those that also support OpenCL.
More information on OpenCL can be found in the OpenCL Programming Guide for Mac .
Deprecated Attributes
There are several renderer and buffer attributes that are no longer recommended either because they are too
narrowly focused or no longer useful. Your application should move away from using any of these attributes:
● The robust attribute (NSOpenGLPFARobust or kCGLPFARobust) specifies only those renderers that do
not have any failure modes associated with a lack of video card resources.
● The multiple-screen attribute (NSOpenGLPFAMultiScreen or kCGLPFAMultiScreen) specifies only
those renderers that can drive more than one screen at a time.
● The multiprocessing-safe attribute (kCGLPFAMPSafe) specifies only those renderers that are thread safe.
This attribute is deprecated in OS X because all renderers can accept commands for threads running on
a second processor. However, this does not mean that all renderers are thread safe or reentrant. See
“Concurrency and OpenGL” (page 148).
● The compliant attribute (NSOpenGLPFACompliant or kCGLPFACompliant) specifies only
OpenGL-compliant renderers. All OS X renderers are OpenGL-compliant, so this attribute is no longer
useful.
● The fullscreen attribute (kCGLPFAFullScreen) requested special fullscreen contexts. The window screen
attribute (kCGLPFAWindow) required the context to support windowed contexts. OS X no longer requires
a special full screen context to be created, as it automatically provides the same performance benefits
with a properly formatted window.
● The offscreen buffer attribute (kCGLPFAOffScreen) selects renderers capable of rendering to offscreen
memory. Instead, use a frame buffer object as the rendering target and read the final results back to
application memory.
Choosing Renderer and Buffer Attributes
OpenCL
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
70● The pixel buffer attributes(kCGLPFAPBuffer and kCGLPFARemotePBuffer are no longer recommended;
use frame buffer objects instead.
● The auxiliary buffers attribute (kCGLPFAAuxBuffers) specifies the number of required auxiliary buffers
your application requires. Auxiliary buffers are not supported by the OpenGL 3.2 Core profile. Because
auxiliary buffers are not supported, the kCGLPFAAuxDepthStencil attribute that modifies it is also
deprecated.
● The accumulation buffersize attribute (kCGLPFAAccumSize)specifiesthe desired size for the accumulation
buffer. Accumulation buffers are not supported by the OpenGL 3.2 Core Profile.
Important: Your application may not use any of the deprecated attributes in conjunction with a profile
other than the legacy profile; if you do, pixel format creation fails.
Choosing Renderer and Buffer Attributes
Deprecated Attributes
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
71A rendering context is a container forstate information. When you designate a rendering context asthe current
rendering context,subsequent OpenGL commands modify that context’sstate, objects attached to that context,
or the drawable object associated with that context. The actual drawing surfaces are never owned by the
rendering context but are created, as needed, when the rendering context is actually attached to a drawable
object. You can attach multiple rendering contexts to the same drawing surfaces. Each context maintains its
own drawing state.
“Drawing to a Window or View” (page 35), “Drawing to the Full Screen” (page 50), and “Drawing
Offscreen” (page 53) show how to create a rendering context and attach it to a drawable object. This chapter
describes advanced ways to interact with rendering contexts.
Update the Rendering Context When the Renderer or Geometry
Changes
A renderer change can occur when the user drags a window from one display to another or when a display is
attached or removed. Geometry changes occur when the display mode changes or when a window is resized
or moved. If your application uses an NSOpenGLView object to maintain the context, it is automatically updated.
An application that creates a custom view to hold the rendering context must track the appropriate system
events and update the context when the geometry or display changes.
Updating a rendering context notifies it of geometry changes; it doesn't flush content. Calling an update
function updates the attached drawable objects and ensures that the renderer is properly updated for any
virtual screen changes. If you don't update the rendering context, you may see rendering artifacts.
The routine that you call for updating determines how events related to renderer and geometry changes are
handled. For applications that use or subclass NSOpenGLView, Cocoa calls the update method automatically.
Applications that create an NSOpenGLContext object manually must call the update method of
NSOpenGLContext directly. For a full-screen Cocoa application, calling the setFullScreen method of
NSOpenGLContext ensures that depth, size, or display changes take affect.
Your application must update the rendering context after the system event but before drawing to the context.
If the drawable object is resized, you may want to issue a glViewport command to ensure that the content
scales properly.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
72
Working with Rendering ContextsNote: Some system-level events(such as display mode changes) that require a context update could
reallocate the buffers of the context; thus you need to redraw the entire scene after all context
updates.
It's important that you don't update rendering contexts more than necessary. Your application should respond
to system-level events and notifications rather than updating every frame. For example, you'll want to respond
to window move and resize operations and to display configuration changes such as a color depth change.
Tracking Renderer Changes
It's fairly straightforward to track geometry changes, but how are renderer changes tracked? This is where the
concept of a virtual screen becomes important (see “Virtual Screens” (page 26)). A change in the virtual screen
indicates a renderer change, a change in renderer capability, or both. When your application detects a window
resize event, window move event, or display change, it should check for a virtual screen change and respond
to the change to ensure that the current application state reflects any changes in renderer capabilities.
Each of the Apple-specific OpenGL APIs has a function that returns the current virtual screen number:
● The currentVirtualScreen method of the NSOpenGLContext class
● The CGLGetVirtualScreen function
The virtual screen number represents an index in the list of virtual screens that were set up specifically for the
pixel format object used for the rendering context. The number is unique to the list but is meaningless otherwise.
When the renderer changes, the limits and extensions available to OpenGL may also change. Your application
should retest the capabilities of the renderer and use these to choose its rendering algorithms appropriately.
See “Determining the OpenGL Capabilities Supported by the Renderer” (page 83).
Updating a Rendering Context for a Custom Cocoa View
If you subclass NSView instead of using the NSOpenGLView class, your application must update the rendering
context. That's due to a slight difference between the events normally handled by the NSView class and those
handled by the NSOpenGLView class. Cocoa does not call a reshape method for the NSView class when the
size changes because that class does not export a reshape method to override. Instead, you need to perform
reshape operations directly in your drawRect: method, looking for changes in view bounds prior to drawing
content. Using this approach provides results that are equivalent to using the reshape method of the
NSOpenGLView class.
Working with Rendering Contexts
Update the Rendering Context When the Renderer or Geometry Changes
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
73Listing 7-1 is a partial implementation of a custom view thatshows how to handle context updates. The update
method is called after move, resize, and display change events and when the surface needs updating. The class
adds an observer to the notification NSViewGlobalFrameDidChangeNotification, which is posted
whenever an NSView object that has attached surfaces (that is, NSOpenGLContext objects) resizes, moves,
or changes coordinate offsets.
It's slightly more complicated to handle changes in the display configuration. For that, you need to register
for the notification NSApplicationDidChangeScreenParametersNotification through the
NSApplication class. This notification is posted whenever the configuration of any of the displays attached
to the computer is changed (either programmatically or when the user changes the settings in the interface).
Listing 7-1 Handling context updates for a custom view
#import
#import
#import
@class NSOpenGLContext, NSOpenGLPixelFormat;
@interface CustomOpenGLView : NSView
{
@private
NSOpenGLContext* _openGLContext;
NSOpenGLPixelFormat* _pixelFormat;
}
- (id)initWithFrame:(NSRect)frameRect
pixelFormat:(NSOpenGLPixelFormat*)format;
- (void)update;
@end
@implementation CustomOpenGLView
- (id)initWithFrame:(NSRect)frameRect
pixelFormat:(NSOpenGLPixelFormat*)format
{
Working with Rendering Contexts
Update the Rendering Context When the Renderer or Geometry Changes
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
74self = [super initWithFrame:frameRect];
if (self != nil) {
_pixelFormat = [format retain];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_surfaceNeedsUpdate:)
name:NSViewGlobalFrameDidChangeNotification
object:self];
}
return self;
}
- (void)dealloc
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSViewGlobalFrameDidChangeNotification
object:self];
[self clearGLContext];
}
- (void)update
{
if ([_openGLContext view] == self) {
[_openGLContext update];
}
}
- (void) _surfaceNeedsUpdate:(NSNotification*)notification
{
[self update];
}
@end
Working with Rendering Contexts
Update the Rendering Context When the Renderer or Geometry Changes
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
75Context Parameters Alter the Context’s Behavior
A rendering context has a variety of parameters that you can set to suit the needs of your OpenGL drawing.
Some of the most useful, and often overlooked, context parameters are discussed in thissection:swap interval,
surface opacity, surface drawing order, and back-buffer size control.
Each of the Apple-specific OpenGL APIs provides a routine forsetting and getting rendering context parameters:
● The setValues:forParameter: method of the NSOpenGLContext class takes as arguments a list of
values and a list of parameters.
● The CGLSetParameter function takes as parameters a rendering context, a constant that specifies an
option, and a value for that option.
Some parameters need to be enabled for their values to take effect. The reference documentation for a
parameter indicates whether a parameter needs to be enabled. See NSOpenGLContext Class Reference , and
CGL Reference .
Swap Interval Allows an Application to Synchronize Updatesto the Screen Refresh
If the swap interval is set to 0 (the default), buffers are swapped as soon as possible, without regard to the
vertical refresh rate of the monitor. If the swap interval is set to any other value, the buffers are swapped only
during the vertical retrace of the monitor. For more information, see “Synchronize with the Screen Refresh
Rate” (page 96).
You can use the following constants to specify that you are setting the swap interval value:
● For Cocoa, use NSOpenGLCPSwapInterval.
●
If you are using the CGL API, use kCGLCPSwapInterval as shown in Listing 7-2.
Listing 7-2 Using CGL to set up synchronization
GLint sync = 1;
// ctx must be a valid context
CGLSetParameter (ctx, kCGLCPSwapInterval, &sync);
Working with Rendering Contexts
Context Parameters Alter the Context’s Behavior
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
76Surface Opacity Specifies How the OpenGL Surface Blends with Surfaces Behind
It
OpenGL surfaces are typically rendered as opaque. Thus the background color for pixels with alpha values of
0.0 is the surface background color. If you set the value of the surface opacity parameter to 0, then the
contents of the surface are blended with the contents of surfaces behind the OpenGL surface. This operation
is equivalent to OpenGL blending with a source contribution proportional to the source alpha and a background
contribution proportional to 1 minus the source alpha. A value of 1 means the surface is opaque (the default);
0 means completely transparent.
You can use the following constants to specify that you are setting the surface opacity value:
● For Cocoa, use NSOpenGLCPSurfaceOpacity.
●
If you are using the CGL API, use kCGLCPSurfaceOpacity as shown in Listing 7-3.
Listing 7-3 Using CGL to set surface opacity
GLint opaque = 0;
// ctx must be a valid context
CGLSetParameter (ctx, kCGLCPSurfaceOpacity, &opaque);
Surface Drawing Order Specifies the Position of the OpenGL Surface Relative to
the Window
A value of 1 means that the position is above the window; a value of –1 specifies a position that is below the
window. When you have overlapping views, setting the order to -1 causes OpenGL to draw underneath, 1
causes OpenGL to draw on top. This parameter is useful for drawing user interface controls on top of an OpenGL
view.
You can use the following constants to specify that you are setting the surface drawing order value:
● For Cocoa, use NSOpenGLCPSurfaceOrder.
●
If you are using the CGL API, use kCGLCPSurfaceOrder as shown in Listing 7-4.
Listing 7-4 Using CGL to set surface drawing order
GLint order = –1; // below window
// ctx must be a valid context
CGLSetParameter (ctx, kCGLCPSurfaceOrder, &order);
Working with Rendering Contexts
Context Parameters Alter the Context’s Behavior
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
77Determining Whether Vertex and Fragment Processing Happens on the GPU
CGL provides two parameters for checking whether the system is using the GPU for processing:
kCGLCPGPUVertexProcessing and kCGLCPGPUFragmentProcessing. To check vertex processing, pass
the vertex constant to the CGLGetParameter function. To check fragment processing, pass the fragment
constant to CGLGetParameter. Listing 7-5 demonstrates how to use these parameters.
Important: Although you can perform these queries at any time, keep in mind that such queries
force an internal state validation, which can impact performance. For best performance, do not use
these queries inside your drawing loop. Instead, perform the queries once at initialization or context
setup time to determine whether OpenGL is using the CPU or the GPU for processing, and then act
appropriately in your drawing loop.
Listing 7-5 Using CGL to check whether the GPU is processing vertices and fragments
BOOL gpuProcessing;
GLint fragmentGPUProcessing, vertexGPUProcessing;
CGLGetParameter (CGLGetCurrentContext(), kCGLCPGPUFragmentProcessing,
&fragmentGPUProcessing);
CGLGetParameter(CGLGetCurrentContext(), kCGLCPGPUVertexProcessing,
&vertexGPUProcessing);
gpuProcessing = (fragmentGPUProcessing && vertexGPUProcessing) ? YES : NO;
Controlling the Back Buffer Size
Normally, the back buffer is the same size as the window or view that it's drawn into, and it changes size when
the window or view changes size. For a window whose size is 720×pixels, the OpenGL back buffer is sized to
match. If the window grows to 1024×768 pixels, for example, then the back buffer is resized as well. If you do
not want this behavior, use the back buffer size control parameter.
Using this parameter fixes the size of the back buffer and lets the system scale the image automatically when
it moves the data to a variable size buffer (see Figure 7-1). The size of the back buffer remains fixed at the size
that you set up regardless of whether the image is resized to display larger onscreen.
You can use the following constants to specify that you are setting the surface backing size:
●
If you are using the CGL API, use kCGLCPSurfaceBackingSize, as shown in Listing 7-6.
Working with Rendering Contexts
Context Parameters Alter the Context’s Behavior
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
78Listing 7-6 Using CGL to set up back buffer size control
GLint dim[2] = {720, 480};
// ctx must be a valid context
CGLSetParameter(ctx, kCGLCPSurfaceBackingSize, dim);
CGLEnable (ctx, kCGLCESurfaceBackingSize);
Figure 7-1 A fixed size back buffer and variable size front buffer
Sharing Rendering Context Resources
A rendering context does not own the drawing objects attached to it, which leaves open the option forsharing.
Rendering contexts can share resources and can be attached to the same drawable object (see Figure 7-2 (page
80)) or to different drawable objects (see Figure 7-3 (page 80)). You set up context sharing—either with more
than one drawable object or with another context—at the time you create a rendering context.
Contexts can share object resources and their associated object state by indicating a shared context at context
creation time. Shared contexts share all texture objects, display lists, vertex programs, fragment programs, and
buffer objects created before and after sharing is initiated. The state of the objects is also shared but not other
Working with Rendering Contexts
Sharing Rendering Context Resources
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
79contextstate,such as current color, texture coordinate settings, matrix and lighting settings, rasterization state,
and texture environment settings. You need to duplicate context state changes as required, but you need to
set up individual objects only once.
Figure 7-2 Shared contexts attached to the same drawable object
Context Context
Drawable
object
Shared
object state
When you create an OpenGL context, you can designate another context whose object resources you want to
share. Allsharing is peer to peer. Shared resources are reference-counted and thus are maintained until explicitly
released or when the last context-sharing resource is released.
Not every context can be shared with every other context. Both contexts must share the same OpenGL profile.
You must also ensure that both contexts share the same set of renderers. You meet these requirements by
ensuring each context uses the same virtual screen list, using either of the following techniques:
● Use the same pixel format object to create all the rendering contexts that you want to share.
● Create pixel format objects using attributes that narrow down the choice to a single display. This practice
ensures that the virtual screen is identical for each pixel format object.
Figure 7-3 Shared contexts and more than one drawable object
Context Context
Drawable
object
Drawable
object
Shared
object state
Setting up shared rendering contextsis very straightforward. Each Apple-specific OpenGL API providesfunctions
with an option to specify a context to share in its context creation routine:
● Use the share argument for the initWithFormat:shareContext: method of the NSOpenGLContext
class. See Listing 7-7 (page 81).
● Use the share parameter for the function CGLCreateContext. See Listing 7-8 (page 82).
Working with Rendering Contexts
Sharing Rendering Context Resources
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
80Listing 7-7 ensures the same virtual screen list by using the same pixel format object for each of the shared
contexts.
Listing 7-7 Setting up an NSOpenGLContext object for sharing
#import
+ (NSOpenGLPixelFormat*)defaultPixelFormat
{
NSOpenGLPixelFormatAttribute attributes [] = {
NSOpenGLPFADoubleBuffer,
(NSOpenGLPixelFormatAttribute)nil };
return [(NSOpenGLPixelFormat *)[NSOpenGLPixelFormat alloc]
initWithAttributes:attribs];
}
- (NSOpenGLContext*)openGLContextWithShareContext:(NSOpenGLContext*)context
{
if (_openGLContext == NULL) {
_openGLContext = [[NSOpenGLContext alloc]
initWithFormat:[[self class] defaultPixelFormat]
shareContext:context];
[_openGLContext makeCurrentContext];
[self prepareOpenGL];
}
return _openGLContext;
}
- (void)prepareOpenGL
{
// Your code here to initialize the OpenGL state
}
Listing 7-8 ensures the same virtual screen list by using the same pixel format object for each of the shared
contexts.
Working with Rendering Contexts
Sharing Rendering Context Resources
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
81Listing 7-8 Setting up a CGL context for sharing
#include
CGLPixelFormatAttribute attrib[] = {kCGLPFADoubleBuffer, 0};
CGLPixelFormatObj pixelFormat = NULL;
Glint numPixelFormats = 0;
CGLContextObj cglContext1 = NULL;
CGLContextObj cglContext2 = NULL;
CGLChoosePixelFormat (attribs, &pixelFormat, &numPixelFormats);
CGLCreateContext(pixelFormat, NULL, &cglContext1);
CGLCreateContext(pixelFormat, cglContext1, &cglContext2);
Working with Rendering Contexts
Sharing Rendering Context Resources
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
82One of the benefits of using OpenGL isthat it is extensible. An extension istypically introduced by one or more
vendors and then later is accepted by the OpenGL Working Group. Some extensions are promoted from a
vendor-specific extension to one shared by more than one vendor, sometimes even being incorporated into
the core OpenGL API. Extensions allow OpenGL to embrace innovation, but require you to verify that the
OpenGL functionality you want to use is available.
Because extensions can be introduced at the vendor level, more than one extension can provide the same
basic functionality. There might also be an ARB-approved extension that has functionality similar to that of a
vendor-specific extension. Your application should prefer core functionality or ARB-approved extensions over
those specific to a particular vendor, when both are offered by the same renderer. This makes it easier to
transparently support new renderers from other vendors.
As particular functionality becomes widely adopted, it can be moved into the core OpenGL API by the ARB. As
a result, functionality that you want to use could be included as an extension, as part of the core API, or both.
For example, the ability to combine texture environments is supported through the
GL_ARB_texture_env_combine and the GL_EXT_texture_env_combine extensions. It's also part of the
core OpenGL version 1.3 API. Although each has similar functionality, they use a different syntax. You may
need to check in several places (core OpenGL API and extension strings) to determine whether a specific
renderer supports functionality that you want to use.
Detecting Functionality
OpenGL hastwo types of commands—those that are part of the core API and those that are part of an extension
to OpenGL. Your application first needs to check for the version of the core OpenGL API and then check for
the available extensions. Keep in mind that OpenGL functionality is available on a per-renderer basis. For
example, a software renderer might notsupport fog effects even though fog effects are available in an OpenGL
extension implemented by a hardware vendor on the same system. For this reason, it's important that you
check for functionality on a per-renderer basis.
Regardless of what functionality you are checking for, the approach is the same. You need to call the OpenGL
function glGetString twice. The first time pass the GL_VERSION constant. The function returns a string that
specifies the version of OpenGL. The second time, pass the GL_EXTENSIONS constant. The function returns a
pointer to an extension name string. The extension name string is a space-delimited list of the OpenGL
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
83
Determining the OpenGL Capabilities Supported
by the Rendererextensions that are supported by the current renderer. This string can be rather long, so do not allocate a
fixed-length string for the return value of the glGetString function. Use a pointer and evaluate the string
in place.
Pass the extension name string to the function gluCheckExtension along with the name of the extension
you want to check for. The gluCheckExtension function returns a Boolean value that indicates whether or
not the extension is available for the current renderer.
If an extension becomes part of the core OpenGL API, OpenGL continues to export the name strings of the
promoted extensions. It also continuesto support the previous versions of any extension that has been exported
in earlier versions of OS X. Because extensions are not typically removed, the methodology you use today to
check for a feature works in future versions of OS X.
Checking for functionality, although fairly straightforward, involves writing a large chunk of code. The best
way to check for OpenGL functionality is to implement a capability-checking function that you call when your
program starts up, and then any time the renderer changes. Listing 8-1 shows a code excerpt that checks for
a few extensions. A detailed explanation for each line of code appears following the listing.
Listing 8-1 Checking for OpenGL functionality
GLint maxRectTextureSize;
GLint myMaxTextureUnits;
GLint myMaxTextureSize;
const GLubyte * strVersion;
const GLubyte * strExt;
float myGLVersion;
GLboolean isVAO, isTexLOD, isColorTable, isFence, isShade,
isTextureRectangle;
strVersion = glGetString (GL_VERSION); // 1
sscanf((char *)strVersion, "%f", &myGLVersion);
strExt = glGetString (GL_EXTENSIONS); // 2
glGetIntegerv(GL_MAX_TEXTURE_UNITS, &myMaxTextureUnits); // 3
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &myMaxTextureSize); // 4
isVAO =
gluCheckExtension ((const GLubyte*)"GL_APPLE_vertex_array_object",strExt); // 5
isFence = gluCheckExtension ((const GLubyte*)"GL_APPLE_fence", strExt); // 6
isShade =
gluCheckExtension ((const GLubyte*)"GL_ARB_shading_language_100", strExt); // 7
Determining the OpenGL Capabilities Supported by the Renderer
Detecting Functionality
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
84isColorTable =
gluCheckExtension ((const GLubyte*)"GL_SGI_color_table", strExt) ||
gluCheckExtension ((const GLubyte*)"GL_ARB_imaging", strExt); // 8
isTexLOD =
gluCheckExtension ((const GLubyte*)"GL_SGIS_texture_lod", strExt) ||
(myGLVersion >= 1.2); // 9
isTextureRectangle = gluCheckExtension ((const GLubyte*)
"GL_EXT_texture_rectangle", strExt);
if (isTextureRectangle)
glGetIntegerv (GL_MAX_RECTANGLE_TEXTURE_SIZE_EXT, &maxRectTextureSize);
else
maxRectTextureSize = 0; // 10
Here is what the code does:
1. Gets a string that specifies the version of OpenGL.
2. Gets the extension name string.
3. Calls the OpenGL function glGetIntegerv to get the value of the attribute passed to it which, in this
case, is the maximum number of texture units.
4. Gets the maximum texture size.
5. Checks whether vertex array objects are supported.
6. Checks for the Apple fence extension.
7. Checks for support for version 1.0 of the OpenGL shading language.
8. Checks for RGBA-format color lookup table support. In this case, the code needs to check for the
vendor-specific string and for the ARB string. If either is present, the functionality is supported.
9. Checks for an extension related to the texture level of detail parameter (LOD). In this case, the code needs
to check for the vendor-specific string and for the OpenGL version. If the vendor string is present or the
OpenGL version is greater than or equal to 1.2, the functionality is supported.
10. Getsthe OpenGL limit for rectangle textures. Forsome extensions,such asthe rectangle texture extension,
it may not be enough to check whether the functionality is supported. You may also need to check the
limits. You can use glGetIntegerv and related functions (glGetBooleanv, glGetDoublev,
glGetFloatv) to obtain a variety of parameter values.
You can extend this example to make a comprehensive functionality-checking routine for your application.
For more details, see the GLCheck.c file in the Cocoa OpenGL sample application.
Determining the OpenGL Capabilities Supported by the Renderer
Detecting Functionality
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
85The code in Listing 8-2 shows one way to query the current renderer. It uses the CGL API, which can be called
from Cocoa applications. In reality, you need to iterate over all displays and all renderers for each display to
get a true picture of the OpenGL functionality available on a particular system. You also need to update your
functionality snapshot each time the list of displays or display configuration changes.
Listing 8-2 Setting up a valid rendering context to get renderer functionality information
#include
#include
CGDirectDisplayID display = CGMainDisplayID (); // 1
CGOpenGLDisplayMask myDisplayMask =
CGDisplayIDToOpenGLDisplayMask (display); // 2
{ // Check capabilities of display represented by display mask
CGLPixelFormatAttribute attribs[] = {kCGLPFADisplayMask,
myDisplayMask,
0}; // 3
CGLPixelFormatObj pixelFormat = NULL;
GLint numPixelFormats = 0;
CGLContextObj myCGLContext = 0;
CGLContextObj curr_ctx = CGLGetCurrentContext (); // 4
CGLChoosePixelFormat (attribs, &pixelFormat, &numPixelFormats); // 5
if (pixelFormat) {
CGLCreateContext (pixelFormat, NULL, &myCGLContext); // 6
CGLDestroyPixelFormat (pixelFormat); // 7
CGLSetCurrentContext (myCGLContext); // 8
if (myCGLContext) {
// Check for capabilities and functionality here
}
}
CGLDestroyContext (myCGLContext); // 9
CGLSetCurrentContext (curr_ctx); // 10
}
Here's what the code does:
1. Gets the display ID of the main display.
Determining the OpenGL Capabilities Supported by the Renderer
Detecting Functionality
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
862. Maps a display ID to an OpenGL mask.
3. Fills a pixel format attributes array with the display mask attribute and the mask value.
4. Saves the current context so that it can be restored later.
5. Gets the pixel format object for the display. The numPixelFormats parameter specifies how many pixel
formats are listed in the pixel format object.
6. Creates a context based on the first pixel format in the list supplied by the pixel format object. Only one
renderer will be associated with this context.
In your application, you would need to iterate through all pixel formats for this display.
7. Destroys the pixel format object when it is no longer needed.
8. Sets the current context to the newly created, single-renderer context. Now you are ready to check for
the functionality supported by the current renderer. See Listing 8-1 (page 84) for an example of
functionality-checking code.
9. Destroys the context because it is no longer needed.
10. Restores the previously saved context as the current context, thus ensuring no intrusion upon the user.
Guidelines for Code That Checks for Functionality
The guidelines in this section ensure that your functionality-checking code is thorough yet efficient.
● Don't rely on what's in a header file. A function declaration in a header file does not ensure that a feature
is supported by the current renderer. Neither does linking against a stub library that exports a function.
● Make sure that a renderer is attached to a valid rendering context before you check the functionality of
that renderer.
● Check the API version or the extension name string for the current renderer before you issue OpenGL
commands.
● Check only once per renderer. After you've determined that the current renderer supports an OpenGL
command, you don't need to check for that functionality again for that renderer.
● Make sure that you are aware of whether a feature is being used as part of the Core OpenGL API or as an
extension. When a feature is implemented both as part of the core OpenGL API and as an extension, it
uses different constants and function names.
Determining the OpenGL Capabilities Supported by the Renderer
Guidelines for Code That Checks for Functionality
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
87OpenGL Renderer Implementation-Dependent Values
The OpenGL specification definesimplementation-dependent valuesthat define the limits of what an OpenGL
implementation is capable of. For example, the maximum size of a texture and the number of texture units
are both common implementation-dependent values that an application is expected to check. Each of these
values provides a minimum value that all conforming OpenGL implementations are expected to support. If
your application’s usage exceeds these minimums, it must check the limit first, and fail gracefully if the
implementation cannot provide the limit desired. Your application may need to load smaller textures, disable
a rendering feature, or choose a different implementation.
Although the specification provides a comprehensive list of these limitations, a few stand out in most OpenGL
applications. Table 8-1 lists values that applications should test if they require more than the minimum values
in the specification.
Table 8-1 Common OpenGL renderer limitations
Maximum size of the texture GL_MAX_TEXTURE_SIZE
Number of depth buffer planes GL_DEPTH_BITS
Number of stencil buffer planes GL_STENCIL_BITS
The limit on the size and complexity of your shaders is a key area you need to test. All graphics hardware
supportslimited memory to pass attributesinto the vertex and fragmentshaders. Your application must either
keep its usage below the minimums as defined in the specification, or it must check the shader limitations
documented in Table 8-2 and choose shaders that are within those limits.
Table 8-2 OpenGL shader limitations
Maximum number of vertex attributes GL_MAX_VERTEX_ATTRIBS
Maximum number of uniform vertex vectors GL_MAX_VERTEX_UNIFORM_COMPONENTS
Maximum number of uniform fragment vectors GL_MAX_FRAGMENT_UNIFORM_COMPONENTS
Maximum number of varying vectors GL_MAX_VARYING_FLOATS
Maximum number of texture units usable in a GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS
vertex shader
Maximum number of texture units usable in a GL_MAX_TEXTURE_IMAGE_UNITS
fragment shader
Determining the OpenGL Capabilities Supported by the Renderer
OpenGL Renderer Implementation-Dependent Values
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
88OpenGL performs many complex operations—transformations, lighting, clipping, texturing, environmental
effects, and so on—on large data sets. The size of your data and the complexity of the calculations performed
on it can impact performance, making your stellar 3D graphics shine less brightly than you'd like. Whether
your application is a game using OpenGL to provide immersive real-time images to the user or an image
processing application more concerned with image quality, use the information in this chapter to help you
design your application.
Visualizing OpenGL
The most common way to visualize OpenGL is as a graphics pipeline, as shown in Figure 9-1 (page 90). Your
application sends vertex and image data, configuration and state changes, and rendering commandsto OpenGL.
Vertices are processed, assembled into primitives, and rasterized into fragments. Each fragment is calculated
and merged into the framebuffer. The pipeline model is useful for identifying exactly what work your application
must perform to generate the results you want. OpenGL allows you to customize each stage of the graphics
pipeline, either through customized shader programs or by configuring a fixed-function pipeline through
OpenGL function calls.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
89
OpenGL Application Design StrategiesIn most implementations, each pipeline stage can act in parallel with the others. This is a key point. If any one
pipeline stage performs too much work, then the other stages sit idle waiting for it to complete. Your design
should balance the work performed in each pipeline stage to the capabilities of the renderer. When you tune
your application’s performance, the firststep is usually to determine which stage the application is bottlenecked
in, and why.
Figure 9-1 OpenGL graphics pipeline
Geometry
Fragment
Framebuffer operations
Texturing
Fog
Alpha, stencil, and depth tests
Framebuffer blending
Primitive assembly
Clipping
Vertex
Application Primitives and image data
Transform and lighting
Another way to visualize OpenGL is as a client-server architecture, as shown in Figure 9-2 (page 91). OpenGL
state changes, texture and vertex data, and rendering commands must all travel from the application to the
OpenGL client. The client transforms these items so that the graphics hardware can understand them, and
then forwards them to the GPU. Not only do these transformations add overhead, but the bandwidth between
the CPU and the graphics hardware is often lower than other parts of the system.
OpenGL Application Design Strategies
Visualizing OpenGL
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
90To achieve great performance, an application must reduce the frequency of callsthey make to OpenGL, minimize
the transformation overhead, and carefully manage the flow of data between the application and the graphics
hardware. For example, OpenGL provides mechanismsthat allow some kinds of data to be cached in dedicated
graphics memory. Caching reusable data in graphics memory reduces the overhead of transmitting data to
the graphics hardware.
Figure 9-2 OpenGL client-server architecture
OpenGL client
OpenGL server
Graphics hardware
Application
OpenGL framework
OpenGL driver
Runs on GPU
Runs on CPU
Designing a High-Performance OpenGL Application
To summarize, a well-designed OpenGL application needs to:
● Exploit parallelism in the OpenGL pipeline.
● Manage data flow between the application and the graphics hardware.
OpenGL Application Design Strategies
Designing a High-Performance OpenGL Application
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
91Figure 9-3 shows a suggested process flow for an application that uses OpenGL to perform animation to the
display.
Figure 9-3 Application model for managing resources
Update dynamic resources Execute rendering commands
Read back results
Present to display
Free up resources
Render loop
Slower process
Faster process
Create static resources
When the application launches, it creates and initializes any static resources it intends to use in the renderer,
encapsulating those resources into OpenGL objects where possible. The goal is to create any object that can
remain unchanged for the runtime of the application. Thistradesincreased initialization time for better rendering
performance. Ideally, complex commands or batches ofstate changesshould be replaced with OpenGL objects
that can be switched in with a single function call. For example, configuring the fixed-function pipeline can
take dozens of function calls. Replace it with a graphics shader that is compiled at initialization time, and you
can switch to a different program with a single function call. In particular, OpenGL objects that are expensive
to create or modify should be created as static objects.
The rendering loop processes all of the items you intend to render to the OpenGL context, then swaps the
buffersto display the resultsto the user. In an animated scene,some data needsto be updated for every frame.
In the inner rendering loop shown in Figure 9-3, the application alternates between updating rendering
resources(possibly creating or modifying OpenGL objectsin the process) and submitting rendering commands
that use those resources. The goal of this inner loop is to balance the workload so that the CPU and GPU are
working in parallel, without blocking each other by using the same resources simultaneously.
OpenGL Application Design Strategies
Designing a High-Performance OpenGL Application
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
92A goal for the inner loop is to avoid copying data back from the graphics processor to the CPU. Operations
that require the CPU to read results back from the graphics hardware are sometimes necessary, but in general
reading back results should be used sparingly. If those results are also used to render the current frame, as
shown in the middle rendering loop, this can be very slow. Copying data from the GPU to the CPU often requires
that some or all previously submitted drawing commands have completed.
After the application submits all drawing commands needed in the frame, it presents the results to the screen.
Alternatively, a non-interactive application might read the final image back to the CPU, but this is also slower
than presenting results to the screen. This step should be performed only for results that must be read back
to the application. For example, you might copy the image in the back buffer to save it to disk.
Finally, when your application is ready to shut down, it deletes static and dynamic resources to make more
hardware resources available to other applications. If your application is moved to the background, releasing
resources to other applications is also good practice.
To summarize the important characteristics of this design:
● Create static resources, whenever practical.
● The inner rendering loop alternates between modifying dynamic resources and submitting rendering
commands. Enough work should be included in this loop so that when the application needs to read or
write to any OpenGL object, the graphics processor has finished processing any commands that used it.
● Avoid reading intermediate rendering results into the application.
The rest of this chapter provides useful OpenGL programming techniques to implement the features of this
rendering loop. Later chapters demonstrate how to apply these general techniquesto specific areas of OpenGL
programming.
●
“Update OpenGL Content Only When Your Data Changes” (page 94)
●
“Avoid Synchronizing and Flushing Operations” (page 96)
●
“Allow OpenGL to Manage Your Resources” (page 99)
●
“Use Optimal Data Types and Formats” (page 102)
●
“Use Double Buffering to Avoid Resource Conflicts” (page 100)
●
“Be Mindful of OpenGL State Variables” (page 101)
●
“Use OpenGL Macros” (page 103)
●
“Replace State Changes with OpenGL Objects” (page 102)
OpenGL Application Design Strategies
Designing a High-Performance OpenGL Application
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
93Update OpenGL Content Only When Your Data Changes
OpenGL applications should avoid recomputing a scene when the data has not changed. This is critical on
portable devices, where power conservation is critical to maximizing battery life. You can ensure that your
application draws only when necessary by following a few simple guidelines:
●
If your application isrendering animation, use a Core Video display link to drive the animation loop. Listing
9-1 (page 94) provides code that allows your application to be notified when a new frame needs to be
displayed. This code also synchronizes image updates to the refresh rate of the display. See “Synchronize
with the Screen Refresh Rate” (page 96) for more information.
●
If your application does not animate its OpenGL content, you should allow the system to regulate drawing.
For example, in Cocoa call the setNeedsDisplay: method when your data changes.
●
If your application does not use a Core Video display link, you should still advance an animation only when
necessary. To determine when to draw the next frame of an animation, calculate the difference between
the current time and the start of the last frame. Use the difference to determine how much to advance
the animation. You can use the Core Foundation function CFAbsoluteTimeGetCurrent to obtain the
current time.
Listing 9-1 Setting up a Core Video display link
@interface MyView : NSOpenGLView
{
CVDisplayLinkRef displayLink; //display link for managing rendering thread
}
@end
- (void)prepareOpenGL
{
// Synchronize buffer swaps with vertical refresh rate
GLint swapInt = 1;
[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
// Create a display link capable of being used with all active displays
CVDisplayLinkCreateWithActiveCGDisplays(&displayLink);
// Set the renderer output callback function
CVDisplayLinkSetOutputCallback(displayLink, &MyDisplayLinkCallback, self);
OpenGL Application Design Strategies
Update OpenGL Content Only When Your Data Changes
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
94// Set the display link for the current renderer
CGLContextObj cglContext = [[self openGLContext] CGLContextObj];
CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj];
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext,
cglPixelFormat);
// Activate the display link
CVDisplayLinkStart(displayLink);
}
// This is the renderer output callback function
static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const
CVTimeStamp* now, const CVTimeStamp* outputTime,
CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
CVReturn result = [(MyView*)displayLinkContext getFrameForTime:outputTime];
return result;
}
- (CVReturn)getFrameForTime:(const CVTimeStamp*)outputTime
{
// Add your drawing codes here
return kCVReturnSuccess;
}
- (void)dealloc
{
// Release the display link
CVDisplayLinkRelease(displayLink);
[super dealloc];
}
OpenGL Application Design Strategies
Update OpenGL Content Only When Your Data Changes
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
95Synchronize with the Screen Refresh Rate
Tearing is a visual anomaly caused when part of the current frame overwrites previous frame data in the
framebuffer before the current frame is fully rendered on the screen. To avoid tearing, applications use a
double-buffered context and synchronize buffer swaps with the screen refresh rate (sometimes called VBL ,
vertical blank , or vsynch ) to eliminate frame tearing.
Note: During development, it's best to disable synchronization so that you can more accurately
benchmark your application. Enable synchronization when you are ready to deploy your application.
The refresh rate of the display limits how often the screen can be refreshed. The screen can be refreshed at
rates that are divisible by integer values. For example, a CRT display that has a refresh rate of 60 Hz can support
screen refresh rates of 60 Hz, 30 Hz, 20 Hz, and 15 Hz. LCD displays do not have a vertical retrace in the CRT
sense and are typically considered to have a fixed refresh rate of 60 Hz.
After you tell the context to swap the buffers, OpenGL must defer any rendering commands that follow that
swap until after the buffers have successfully been exchanged. Applications that attempt to draw to the screen
during this waiting period waste time that could be spent performing other drawing operations or saving
battery life and minimizing fan operation.
Listing 9-2 shows how an NSOpenGLView object can synchronize with the screen refresh rate; you can use a
similar approach if your application uses CGL contexts. It assumes that you set up the context for double
buffering. The swap interval can be set only to 0 or 1. If the swap interval is set to 1, the buffers are swapped
only during the vertical retrace.
Listing 9-2 Setting up synchronization
GLint swapInterval = 1;
[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
Avoid Synchronizing and Flushing Operations
OpenGL is not required to execute most commandsimmediately. Often, they are queued to a command buffer
and read and executed by the hardware at a later time. Usually, OpenGL waits until the application has queued
up a significant number of commands before sending the buffer to the hardware—allowing the graphics
hardware to execute commands in batches is often more efficient. However, some OpenGL functions must
flush the buffer immediately. Other functions not only flush the buffer, but also block until previously submitted
commands have completed before returning control to the application. Your application should restrict the
OpenGL Application Design Strategies
Avoid Synchronizing and Flushing Operations
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
96use of flushing and synchronizing commands only to those cases where that behavior is necessary. Excessive
use of flushing or synchronizing commands add additional stalls waiting for the hardware to finish rendering.
On a single-buffered context, flushing may also cause visual anomalies, such as flickering or tearing.
These situations require OpenGL to submit the command buffer to the hardware for execution.
● The function glFlush waits until commands are submitted but does not wait for the commands to finish
executing.
● The function glFinish waits for all previously submitted commands to complete executing.
● Functions that retrieve OpenGL state (for example, glGetError), also wait for submitted commands to
complete.
● Buffer swapping routines (the flushBuffer method of the NSOpenGLContext class or the
CGLFlushDrawable function) implicitly call glFlush. Note that when using the NSOpenGLContext
class or the CGL API, the term flush actually refers to a buffer-swapping operation. For single-buffered
contexts, glFlush and glFinish are equivalent to a swap operation, since all rendering is taking place
directly in the front buffer.
● The command buffer is full.
Using glFlush Effectively
Most of the time you don't need to call glFlush to move image data to the screen. There are only a few cases
that require you to call the glFlush function:
●
If your application submits rendering commands that use a particular OpenGL object, and it intends to
modify that object in the near future. If you attempt to modify an OpenGL object that has pending drawing
commands, your application may be forced to wait until those commands have been completed. In this
situation, calling glFlush ensures that the hardware begins processing commands immediately. After
flushing the command buffer, your application should perform work that does not need that resource. It
can perform other work (even modifying other OpenGL objects).
● Your application needs to change the drawable object associated with the rendering context. Before you
can switch to another drawable object, you must call glFlush to ensure that all commands written in
the command queue for the previous drawable object have been submitted.
● When two contexts share an OpenGL object. After submitting any OpenGL commands, call glFlush
before switching to the other context.
● To keep drawing synchronized across multiple threads and prevent command buffer corruption, each
thread should submit its rendering commands and then call glFlush.
OpenGL Application Design Strategies
Avoid Synchronizing and Flushing Operations
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
97Avoid Querying OpenGL State
Calls to glGet*(), including glGetError(), may require OpenGL to execute previous commands before
retrieving any state variables. This synchronization forces the graphics hardware to run lockstep with the CPU,
reducing opportunities for parallelism.
Your application should keep shadow copies of any OpenGL state that you need to query, and maintain these
shadow copies as you change the state.
When errors occur, OpenGL sets an error flag that you can retrieve with the function glGetError. During
development, it's crucial that your code contains error checking routines, not only for the standard OpenGL
calls, but for the Apple-specific functions provided by the CGL API. If you are developing a performance-critical
application, retrieve error information only in the debugging phase. Calling glGetError excessively in a
release build degrades performance.
Use Fences for Finer-Grained Synchronization
Avoid using glFinish in your application, because it waits until all previously submitted commands are
completed before returning control to your application. Instead, you should use the fence extension
(APPLE_fence). This extension was created to provide the level of granularity that is not provided by glFinish.
A fence is a token used to mark the current point in the command stream. When used correctly, it allows you
to ensure that a specific series of commands has been completed. A fence helps coordinate activity between
the CPU and the GPU when they are using the same resources.
Follow these steps to set up and use a fence:
1. At initialization time, create the fence object by calling the function glGenFencesAPPLE.
GLint myFence;
glGenFencesAPPLE(1,&myFence);
2. Call the OpenGL functions that must complete prior to the fence.
3. Set up the fence by calling the function glSetFenceAPPLE. Thisfunction inserts a token into the command
stream and sets the fence state to false.
void glSetFenceAPPLE(GLuint fence);
fence specifies the token to insert. For example:
glSetFenceAPPLE(myFence);
OpenGL Application Design Strategies
Avoid Synchronizing and Flushing Operations
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
984. Call glFlush to force the commands to be sent to the hardware. This step is optional, but recommended
to ensure that the hardware begins processing OpenGL commands.
5. Perform other work in your application.
6. Wait for all OpenGL commands issued prior to the fence to complete by calling the function
glFinishFenceAPPLE.
glFinishFenceAPPLE(myFence);
As an alternative to calling glFinishFenceAPPLE, you can call glTestFenceAPPLE to determine whether
the fence has been reached. The advantage of testing the fence is that your application does not block
waiting for the fence to complete. This is useful if your application can continue processing other work
while waiting for the fence to trigger.
glTestFenceAPPLE(myFence);
7. When your application no longer needsthe fence, delete it by calling the function glDeleteFencesAPPLE.
glDeleteFencesAPPLE(1,&myFence);
There is an art to determining where to insert a fence in the command stream. If you insert a fence for too few
drawing commands, you risk having your application stall while it waits for drawing to complete. You'll want
to set a fence so your application operates as asynchronously as possible without stalling.
The fence extension also lets you synchronize buffer updates for objects such as vertex arrays and textures.
For that you call the function glFinishObjectAPPLE, supplying an object name along with the token.
For detailed information on this extension, see the OpenGL specification for the Apple fence extension.
Allow OpenGL to Manage Your Resources
OpenGL allows many data types to be stored persistently inside OpenGL. Creating OpenGL objects to store
vertex, texture, or other forms of data allows OpenGL to reduce the overhead of transforming the data and
sending them to the graphics processor. If data is used more frequently than it is modified, OpenGL can
substantially improve the performance of your application.
OpenGL Application Design Strategies
Allow OpenGL to Manage Your Resources
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
99OpenGL allows your application to hint how it intends to use the data. These hints allow OpenGL to make an
informed choice of how to process your data. For example, static data might be placed in high-speed graphics
memory directly connected to the graphics processor. Data that changes frequently might be kept in main
memory and accessed by the graphics hardware through DMA.
Use Double Buffering to Avoid Resource Conflicts
Resource conflicts occur when your application and OpenGL want to access a resource at the same time. When
one participant attempts to modify an OpenGL object being used by the other, one of two problems results:
● The participant that wantsto modify the object blocks until it is no longer in use. Then the other participant
is not allowed to read from or write to the object until the modifications are complete. This is safe, but
these can be hidden bottlenecks in your application.
● Some extensions allow OpenGL to access application memory that can be simultaneously accessed by
the application. In this situation, synchronizing between the two participants is left to the application to
manage. Your application calls glFlush to force OpenGL to execute commands and uses a fence or
glFinish to ensure that no commands that access that memory are pending.
Whether your application relies on OpenGL to synchronize access to a resource, or it manually synchronizes
access, resource contention forces one of the participants to wait, rather than allowing them both to execute
in parallel. Figure 9-4 demonstrates this problem. There is only a single buffer for vertex data, which both the
application and OpenGL want to use and therefore the application must wait until the GPU finishes processing
commands before it modifies the data.
Figure 9-4 Single-buffered vertex array data
CPU
GPU
Vertex array 1 Vertex array 1
Vertex array 1 Vertex array 1
glFlush glFlush
glFinishObject(..., 1) glFinishObject(..., 1)
Time Frame 1 Frame 2
OpenGL Application Design Strategies
Use Double Buffering to Avoid Resource Conflicts
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
100To solve this problem, your application could fill this idle time with other processing, even other OpenGL
processing that does not need the objects in question. If you need to process more OpenGL commands, the
solution is to create two of the same resource type and let each participant access a resource. Figure 9-5
illustrates the double-buffered approach. While the GPU operates on one set of vertex array data, the CPU is
modifying the other. After the initialstartup, neither processing unit isidle. This example uses a fence to ensure
that access to each buffer is synchronized.
Figure 9-5 Double-buffered vertex array data
CPU Vertex array 1 Vertex array 1
GPU Vertex array 1 Vertex array 1
Vertex array 2 Vertex array 2
Vertex array 2 Vertex array 2
glFlush glFlush glFlush glFlush
glFinishObject(..., 1) glFinishObject(..., 1)
glFinishObject(..., 2) glFinishObject(..., 2)
Time Frame 1 Frame 2 Frame 3 Frame 4
Double buffering issufficient for most applications, but it requiresthat both participantsfinish processing their
commands before a swap can occur. For a traditional producer-consumer problem, more than two buffers
may prevent a participant from blocking. With triple buffering, the producer and consumer each have a buffer,
with a third idle buffer. If the producer finishes before the consumer finishes processing commands, it takes
the idle buffer and continues to process commands. In this situation, the producer idles only if the consumer
falls badly behind.
Be Mindful of OpenGL State Variables
The hardware has one current state, which is compiled and cached. Switching state is expensive, so it's best
to design your application to minimize state switches.
Don't set a state that's already set. Once a feature is enabled, it does not need to be enabled again. Calling an
enable function more than once does nothing except waste time because OpenGL does not check the state
of a feature when you call glEnable or glDisable. For instance, if you call glEnable(GL_LIGHTING) more
than once, OpenGL does not check to see if the lighting state is already enabled. It simply updates the state
value even if that value is identical to the current value.
OpenGL Application Design Strategies
Be Mindful of OpenGL State Variables
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
101You can avoid setting a state more than necessary by using dedicated setup or shutdown routines rather than
putting such callsin a drawing loop. Setup and shutdown routines are also useful for turning on and off features
that achieve a specific visual effect—for example, when drawing a wire-frame outline around a textured
polygon.
If you are drawing 2D images, disable all irrelevant state variables, similar to what's shown in Listing 9-3.
Listing 9-3 Disabling state variables
glDisable(GL_DITHER);
glDisable(GL_ALPHA_TEST);
glDisable(GL_BLEND);
glDisable(GL_STENCIL_TEST);
glDisable(GL_FOG);
glDisable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glPixelZoom(1.0,1.0);
// Disable other state variables as appropriate.
Replace State Changes with OpenGL Objects
The “Be Mindful of OpenGL State Variables” (page 101) section suggests that reducing the number of state
changes can improve performance. Some OpenGL extensions also allow you to create objects that collect
multiple OpenGL state changes into an object that can be bound with a single function call. Where such
techniques are available, they are recommended. For example, configuring the fixed-function pipeline requires
many function calls to change the state of the various operators. Not only does this incur overhead for each
function called, but the code is more complex and difficult to manage. Instead, use a shader. A shader, once
compiled, can have the same effect but requires only a single call to glUseProgram.
Other examples of objects that take the place of multiple state changes include the “Vertex Array Range
Extension” (page 113) and “Uniform Buffers” (page 143).
Use Optimal Data Types and Formats
If you don't use data types and formats that are native to the graphics hardware, OpenGL must convert those
data types into a format that the graphics hardware understands.
OpenGL Application Design Strategies
Replace State Changes with OpenGL Objects
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
102For vertex data, use GLfloat, GLshort, or GLubyte data types. Most graphics hardware handle these types
natively.
For texture data, you’ll get the best performance if you use the following format and data type combination:
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV
These format and data type combinations also provide acceptable performance:
GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV
GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_REV_APPLE
The combination GL_RGBA and GL_UNSIGNED_BYTE needs to be swizzled by many cards when the data is
loaded, so it's not recommended.
Use OpenGL Macros
OpenGL performs a global context and renderer lookup for each command it executesto ensure that all OpenGL
commands are issued to the correct rendering context and renderer. There is significant overhead associated
with these lookups; applicationsthat have extremely high call frequenciesmay find that the overheadmeasurably
affects performance. OS X allows your application to use macros to provide a local context variable and cache
the current renderer in that variable. You get more benefit from using macros when your code makes millions
of function calls per second.
Before implementing this technique, consider carefully whether you can redesign your application to perform
less function calls. Frequently changing OpenGL state, pushing or popping matrices, or even submitting one
vertex at a time are all examples of techniques that should be replaced with more efficient operations.
You can use the CGL macro header (CGL/CGLMacro.h) if your application uses CGL from a Cocoa application.
You must define the local variable cgl_ctx to be equal to the current context. Listing 9-4 shows what's needed
to set up macro use for the CGL API. First, you need to include the correct macro header. Then, you must set
the current context.
Listing 9-4 Using CGL macros
#include // include the header
CGL_MACRO_DECLARE_VARIABLES // set the current context
glBegin (GL_QUADS); // This code now uses the macro
// draw here
glEnd ();
OpenGL Application Design Strategies
Use OpenGL Macros
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
103Complex shapes and detailed 3D models require large amounts of vertex data to describe them in OpenGL.
Moving vertex data from your application to the graphics hardware incurs a performance cost that can be
quite large depending on the size of the data set.
Figure 10-1 Vertex data sets can be quite large
Applications that use large vertex data sets can adopt one or more of the strategies described in “OpenGL
Application Design Strategies” (page 89) to optimize how vertex data is delivered to OpenGL.This chapter
expands on those best practices with specific techniques for working with vertex data.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
104
Best Practices for Working with Vertex DataUnderstand How Vertex Data Flows Through OpenGL
Understanding how vertex data flows through OpenGL is important to choosing strategies for handling the
data. Vertex data enters into the vertex stage, where it is processed by either the built-in fixed function vertex
stage or a custom vertex.
Figure 10-2 Vertex data path
Rasterization
Fragment shading
and per-fragment
operations
Per-pixel
operations
Texture
assembly
Framebuffer
Vertex shading
and per-vertex
operations
Pixel data
Vertex data
Figure 10-3 takes a closer look at the vertex data path when using immediate mode. Without any optimizations,
your vertex data may be copied at various points in the data path. If your application uses immediate mode
to each vertex separately, calls to OpenGL first modify the current vertex, which is copied into the command
buffer whenever your application makes a glVertex* call. Thisis not only expensive in terms of copy operations,
but also in function overhead to specify each vertex.
Figure 10-3 Immediate mode requires a copy of the current vertex data
GPU
VRAM
Copy
Copy
Original
Command buffer
Current vertex
Application
Best Practices for Working with Vertex Data
Understand How Vertex Data Flows Through OpenGL
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
105The OpenGL commands glDrawRangeElements, glDrawElements, and glDrawArrays render multiple
geometric primitives from array data, using very few subroutine calls. Listing 10-1 shows a typical
implementation. Your application creates a vertex structure that holds all the elements for each vertex. For
each element , you enable a client array and provide a pointer and offset to OpenGL so that it knows how to
find those elements.
Listing 10-1 Submitting vertex data using glDrawElements.
typedef struct _vertexStruct
{
GLfloat position[2];
GLubyte color[4];
} vertexStruct;
void DrawGeometry()
{
const vertexStruct vertices[] = {...};
const GLubyte indices[] = {...};
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, sizeof(vertexStruct), &vertices[0].position);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vertexStruct), &vertices[0].color);
glDrawElements(GL_TRIANGLE_STRIP, sizeof(indices)/sizeof(GLubyte),
GL_UNSIGNED_BYTE, indices);
}
Each time you call glDrawElements, OpenGL must copy all of the vertex data into the command buffer, which
is later copied to the hardware. The copy overhead is still expensive.
Best Practices for Working with Vertex Data
Understand How Vertex Data Flows Through OpenGL
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
106Techniques for Handling Vertex Data
Avoiding unnecessary copies of your vertex data is critical to application performance. Thissection summarizes
common techniques for managing your vertex data using either built-in functionality or OpenGL extensions.
Before using these techniques, you must ensure that the necessary functions are available to your application.
See “Detecting Functionality” (page 83).
● Avoid the use of glBegin and glEnd to specify your vertex data. The function and copying overhead
makes this path useful only for very small data sets. Also, applications written with glBegin and glEnd
are not portable to OpenGL ES on iOS.
● Minimize data type conversions by supplying OpenGL data types for vertex data. Use GLfloat, GLshort,
or GLubyte data types because most graphics processors handle these types natively. If you use some
other type, then OpenGL may need to perform a costly data conversion.
● The preferred way to manage your vertex data is with vertex buffer objects. Vertex buffer objects are
buffers owned by OpenGL that hold your vertex information. These buffers allow OpenGL to place your
vertex data into memory that is accessible to the graphics hardware. See “Vertex Buffers” (page 107) for
more information.
●
If vertex buffer objects are not available, your application can search for the
GL_APPLE_vertex_array_range and APPLE_fence extensions. Vertex array ranges allow you to
prevent OpenGL from copying your vertex data into the command buffer. Instead, your application must
avoid modifying or deleting the vertex data until OpenGL finishes executing drawing commands. This
solution requires more effort from the application, and is not compatible with other platforms, including
iOS. See “Vertex Array Range Extension” (page 113) for more information.
● Complex vertex operations require many array pointers to be enabled and set before you call
glDrawElements. The GL_APPLE_vertex_array_object extension allows your application to
consolidate a group of array pointers into a single object. Your application switches multiple pointers by
binding a single vertex array object, reducing the overhead of changing state. See “Vertex Array
Object” (page 116).
● Use double buffering to reduce resource contention between your application and OpenGL. See “Use
Double Buffering to Avoid Resource Conflicts” (page 100).
●
If you need to compute new vertex information between frames, consider using vertex shaders and buffer
objects to perform and store the calculations.
Vertex Buffers
Vertex buffers are available as a core feature starting in OpenGL 1.5, and on earlier versions of OpenGL through
the vertex buffer object extension (GL_ARB_vertex_buffer_object). Vertex buffers are used to improve
the throughput of static or dynamic vertex data in your application.
Best Practices for Working with Vertex Data
Techniques for Handling Vertex Data
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
107A buffer object is a chunk of memory owned by OpenGL. Your application reads from or writes to the buffer
using OpenGL callssuch as glBufferData, glBufferSubData, and glGetBufferSubData. Your application
can also gain a pointer to this memory, an operation referred to as mapping a buffer. OpenGL prevents your
application and itself from simultaneously using the data stored in the buffer. When your application maps a
buffer or attempts to modify it, OpenGL may block until previous drawing commands have completed.
Using Vertex Buffers
You can set up and use vertex buffers by following these steps:
1. Call the function glGenBuffers to create a new name for a buffer object.
void glGenBuffers(sizei n, uint *buffers );
n is the number of buffers you wish to create identifiers for.
buffers specifies a pointer to memory to store the buffer names.
2. Call the function glBindBuffer to bind an unused name to a buffer object. After this call, the newly
created buffer object is initialized with a memory buffer of size zero and a default state. (For the default
setting, see the OpenGL specification for ARB_vertex_buffer_object.)
void glBindBuffer(GLenum target, GLuint buffer);
target must be set to GL_ARRAY_BUFFER.
buffer specifies the unique name for the buffer object.
3. Fill the buffer object by calling the function glBufferData. Essentially, this call uploads your data to the
GPU.
void glBufferData(GLenum target, sizeiptr size,
const GLvoid *data, GLenum usage);
target must be set to GL_ARRAY_BUFFER.
size specifies the size of the data store.
*data points to the source data. If this is not NULL, the source data is copied to the data stored of the
buffer object. If NULL, the contents of the data store are undefined.
Best Practices for Working with Vertex Data
Vertex Buffers
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
108usage is a constant that provides a hint as to how your application plans to use the data stored in the
buffer object. These examples use GL_STREAM_DRAW, which indicates that the application plans to both
modify and draw using the buffer, and GL_STATIC_DRAW, which indicates that the application will define
the data once but use it to draw many times. For more details on buffer hints,see “Buffer Usage Hints” (page
110)
4. Enable the vertex array by calling glEnableClientState and supplying the GL_VERTEX_ARRAY constant.
5. Point to the contents of the vertex buffer object by calling a function such as glVertexPointer. Instead
of providing a pointer, you provide an offset into the vertex buffer object.
6. To update the data in the buffer object, your application calls glMapBuffer. Mapping the buffer prevents
the GPU from operating on the data, and gives your application a pointer to memory it can use to update
the buffer.
void *glMapBuffer(GLenum target, GLenum access);
target must be set to GL_ARRAY_BUFFER.
access indicatesthe operations you plan to performon the data. You can supply READ_ONLY, WRITE_ONLY,
or READ_WRITE.
7. Write pixel data to the pointer received from the call to glMapBuffer.
8. When your application hasfinished modifying the buffer contents, call the function glUnmapBuffer. You
must supply GL_ARRAY_BUFFER as the parameter to this function. Once the buffer is unmapped, the
pointer is no longer valid, and the buffer’s contents are uploaded again to the GPU.
Listing 10-2 shows code that usesthe vertex buffer object extension for dynamic data. This example overwrites
all of the vertex data during every draw operation.
Listing 10-2 Using the vertex buffer object extension with dynamic data
// To set up the vertex buffer object extension
#define BUFFER_OFFSET(i) ((char*)NULL + (i))
glBindBuffer(GL_ARRAY_BUFFER, myBufferName);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, stride, BUFFER_OFFSET(0));
// When you want to draw using the vertex data
draw_loop {
Best Practices for Working with Vertex Data
Vertex Buffers
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
109glBufferData(GL_ARRAY_BUFFER, bufferSize, NULL, GL_STREAM_DRAW);
my_vertex_pointer = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
GenerateMyDynamicVertexData(my_vertex_pointer);
glUnmapBuffer(GL_ARRAY_BUFFER);
PerformDrawing();
}
Listing 10-3 shows code that uses the vertex buffer object extension with static data.
Listing 10-3 Using the vertex buffer object extension with static data
// To set up the vertex buffer object extension
#define BUFFER_OFFSET(i) ((char*)NULL + (i))
glBindBuffer(GL_ARRAY_BUFFER, myBufferName);
glBufferData(GL_ARRAY_BUFFER, bufferSize, NULL, GL_STATIC_DRAW);
GLvoid* my_vertex_pointer = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
GenerateMyStaticVertexData(my_vertex_pointer);
glUnmapBuffer(GL_ARRAY_BUFFER);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, stride, BUFFER_OFFSET(0));
// When you want to draw using the vertex data
draw_loop {
PerformDrawing();
}
Buffer Usage Hints
A key advantage of buffer objectsisthat the application can provide information on how it usesthe data stored
in each buffer. For example, Listing 10-2 and Listing 10-3 differentiated between cases where the data were
expected to never change (GL_STATIC_DRAW) and cases where the buffer data might change
(GL_DYNAMIC_DRAW). The usage parameter allows an OpenGL renderer to alter its strategy for allocating the
vertex buffer to improve performance. For example, static buffers may be allocated directly in GPU memory,
while dynamic buffers may be stored in main memory and retrieved by the GPU via DMA.
If OpenGL ES compatibility is useful to you, you should limit your usage hints to one of three usage cases:
Best Practices for Working with Vertex Data
Vertex Buffers
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
110● GL_STATIC_DRAW should be used for vertex data that isspecified once and never changed. Your application
should create these vertex buffers during initialization and use them repeatedly until your application
shuts down.
● GL_DYNAMIC_DRAW should be used when the buffer is expected to change after it is created. Your
application should still allocate these buffers during initialization and periodically update them by mapping
the buffer.
● GL_STREAM_DRAW is used when your application needs to create transient geometry that is rendered and
then discarded. This is most useful when your application must dynamically change vertex data every
frame in a way that cannot be performed in a vertex shader. To use a stream vertex buffer, your application
initially fills the buffer using glBufferData, then alternates between drawing using the buffer and
modifying the buffer.
Other usage constants are detailed in the vertex buffer specification.
If different elements in your vertex format have different usage characteristics, you may want to split the
elements into one structure for each usage pattern and allocate a vertex buffer for each. Listing 10-4 shows
how to implement this. In this example, position data is expected to be the same in each frame, while color
data may be animated in every frame.
Listing 10-4 Geometry with different usage patterns
typedef struct _vertexStatic
{
GLfloat position[2];
} vertexStatic;
typedef struct _vertexDynamic
{
GLubyte color[4];
} vertexDynamic;
// Separate buffers for static and dynamic data.
GLuint staticBuffer;
GLuint dynamicBuffer;
GLuint indexBuffer;
const vertexStatic staticVertexData[] = {...};
vertexDynamic dynamicVertexData[] = {...};
Best Practices for Working with Vertex Data
Vertex Buffers
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
111const GLubyte indices[] = {...};
void CreateBuffers()
{
glGenBuffers(1, &staticBuffer);
glGenBuffers(1, &dynamicBuffer);
glGenBuffers(1, &indexBuffer);
// Static position data
glBindBuffer(GL_ARRAY_BUFFER, staticBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(staticVertexData), staticVertexData,
GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// Dynamic color data
// While not shown here, the expectation is that the data in this buffer changes
between frames.
glBindBuffer(GL_ARRAY_BUFFER, dynamicBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(dynamicVertexData), dynamicVertexData,
GL_DYNAMIC_DRAW);
}
void DrawUsingVertexBuffers()
{
glBindBuffer(GL_ARRAY_BUFFER, staticBuffer);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, sizeof(vertexStatic),
(void*)offsetof(vertexStatic,position));
glBindBuffer(GL_ARRAY_BUFFER, dynamicBuffer);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vertexDynamic),
(void*)offsetof(vertexDynamic,color));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
Best Practices for Working with Vertex Data
Vertex Buffers
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
112glDrawElements(GL_TRIANGLE_STRIP, sizeof(indices)/sizeof(GLubyte),
GL_UNSIGNED_BYTE, (void*)0);
}
Flush Buffer Range Extension
When your application unmaps a vertex buffer, the OpenGL implementation may copy the full contents of the
buffer to the graphics hardware. If your application changes only a subset of a large buffer, this is inefficient.
The APPLE_flush_buffer_range extension allows your application to tell OpenGL exactly which portions
of the buffer were modified, allowing it to send only the changed data to the graphics hardware.
To use the flush buffer range extension, follow these steps:
1. Turn on the flush buffer extension by calling glBufferParameteriAPPLE.
glBufferParameteriAPPLE(GL_ARRAY_BUFFER,GL_BUFFER_FLUSHING_UNMAP_APPLE,
GL_FALSE);
This disables the normal flushing behavior of OpenGL.
2. Before you unmap a buffer, you must call glFlushMappedBufferRangeAPPLE for each range of the
buffer that was modified by the application.
void glFlushMappedBufferRangeAPPLE(enum target, intptr offset, sizeiptr
size);
target is the type of buffer being modified; for vertex data it’s ARRAY_BUFFER.
offset is the offset into the buffer for the modified data.
size is the length of the modified data in bytes.
3. Call glUnmapBuffer. OpenGL unmaps the buffer, but it is required to update only the portions of the
buffer your application explicitly marked as changed.
For more information see the APPLE_flush_buffer_range specification.
Vertex Array Range Extension
The vertex array range extension (APPLE_vertex_array_range) lets you define a region of memory for your
vertex data. The OpenGL driver can optimize memory usage by creating a single memory mapping for your
vertex data. You can also provide a hint as to how the data should be stored: cached or shared. The cached
Best Practices for Working with Vertex Data
Vertex Array Range Extension
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
113option specifiesto cache vertex data in video memory. The shared option indicatesthat data should be mapped
into a region of memory that allows the GPU to access the vertex data directly using DMA transfer. This option
is best for dynamic data. If you use shared memory, you'll need to double buffer your data.
You can set up and use the vertex array range extension by following these steps:
1. Enable the extension by calling glEnableClientState and supplying the
GL_VERTEX_ARRAY_RANGE_APPLE constant.
2. Allocate storage for the vertex data. You are responsible for maintaining storage for the data.
3. Define an array of vertex data by calling a function such as glVertexPointer. You need to supply a
pointer to your data.
4. Optionally set up a hint about handling the storage of the array data by calling the function
glVertexArrayParameteriAPPLE.
GLvoid glVertexArrayParameteriAPPLE(GLenum pname, GLint param);
pname must be VERTEX_ARRAY_STORAGE_HINT_APPLE.
param is a hint that specifies how your application expects to use the data. OpenGL uses this hint to
optimize performance. You can supply either STORAGE_SHARED_APPLE or STORAGE_CACHED_APPLE.
The default value is STORAGE_SHARED_APPLE, which indicates that the vertex data is dynamic and that
OpenGL should use optimization and flushing techniques suitable for this kind of data. If you expect the
supplied data to be static, use STORAGE_CACHED_APPLE so that OpenGL can optimize appropriately.
5. Call the OpenGL function glVertexArrayRangeAPPLE to establish the data set.
void glVertexArrayRangeAPPLE(GLsizei length, GLvoid *pointer);
length specifies the length of the vertex array range. The length is typically the number of unsigned
bytes.
*pointer points to the base of the vertex array range.
6. Draw with the vertex data using standard OpenGL vertex array commands.
7. If you need to modify the vertex data,set a fence object after you’ve submitted all the drawing commands.
See “Use Fences for Finer-Grained Synchronization” (page 98)
8. Perform other work so that the GPU has time to process the drawing commands that use the vertex array.
9. Call glFinishFenceAPPLE to gain access to the vertex array.
10. Modify the data in the vertex array.
11. Call glFlushVertexArrayRangeAPPLE.
Best Practices for Working with Vertex Data
Vertex Array Range Extension
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
114void glFlushVertexArrayRangeAPPLE(GLsizei length, GLvoid *pointer);
length specifies the length of the vertex array range, in bytes.
*pointer points to the base of the vertex array range.
For dynamic data, each time you change the data, you need to maintain synchronicity by calling
glFlushVertexArrayRangeAPPLE. You supply as parameters an array size and a pointer to an array,
which can be a subset of the data, as long as it includes all of the data that changed. Contrary to the name
of the function, glFlushVertexArrayRangeAPPLE doesn't actually flush data like the OpenGL function
glFlush does. It simply makes OpenGL aware that the data has changed.
Listing 10-5 shows code thatsets up and usesthe vertex array range extension with dynamic data. It overwrites
all of the vertex data during each iteration through the drawing loop. The call to the glFinishFenceAPPLE
command guaranteesthat the CPU and the GPU don't accessthe data at the same time. Although this example
calls the glFinishFenceAPPLE function almost immediately after setting the fence, in reality you need to
separate these calls to allow parallel operation of the GPU and CPU. To see how that's done, read “Use Double
Buffering to Avoid Resource Conflicts” (page 100).
Listing 10-5 Using the vertex array range extension with dynamic data
// To set up the vertex array range extension
glVertexArrayParameteriAPPLE(GL_VERTEX_ARRAY_STORAGE_HINT_APPLE,
GL_STORAGE_SHARED_APPLE);
glVertexArrayRangeAPPLE(buffer_size, my_vertex_pointer);
glEnableClientState(GL_VERTEX_ARRAY_RANGE_APPLE);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, my_vertex_pointer);
glSetFenceAPPLE(my_fence);
// When you want to draw using the vertex data
draw_loop {
glFinishFenceAPPLE(my_fence);
GenerateMyDynamicVertexData(my_vertex_pointer);
glFlushVertexArrayRangeAPPLE(buffer_size, my_vertex_pointer);
PerformDrawing();
glSetFenceAPPLE(my_fence);
}
Best Practices for Working with Vertex Data
Vertex Array Range Extension
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
115Listing 10-6 shows code that usesthe vertex array range extension with static data. Unlike the setup for dynamic
data, the setup forstatic data includes using the hint for cached data. Because the data isstatic, it's unnecessary
to set a fence.
Listing 10-6 Using the vertex array range extension with static data
// To set up the vertex array range extension
GenerateMyStaticVertexData(my_vertex_pointer);
glVertexArrayParameteriAPPLE(GL_VERTEX_ARRAY_STORAGE_HINT_APPLE,
GL_STORAGE_CACHED_APPLE);
glVertexArrayRangeAPPLE(array_size, my_vertex_pointer);
glEnableClientState(GL_VERTEX_ARRAY_RANGE_APPLE);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, stride, my_vertex_pointer);
// When you want to draw using the vertex data
draw_loop {
PerformDrawing();
}
For detailed information on this extension, see the OpenGL specification for the vertex array range extension.
Vertex Array Object
Look at the DrawUsingVertexBuffers function in Listing 10-4 (page 111). It configures buffer pointers for
position, color, and indexing before calling glDrawElements. A more complex vertex structure may require
additional buffer pointers to be enabled and changed before you can finally draw your geometry. If your
application swaps frequently between multiple configurations of elements, changing these parameters adds
significant overhead to your application. The APPLE_vertex_array_object extension allows you to combine
a collection of buffer pointers into a single OpenGL object, allowing you to change all the buffer pointers by
binding a different vertex array object.
To use this extension, follow these steps during your application’s initialization routines:
1. Generate a vertex array object for a configuration of pointers you wish to use together.
Best Practices for Working with Vertex Data
Vertex Array Object
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
116void glGenVertexArraysAPPLE(sizei n, const uint *arrays);
n is the number of arrays you wish to create identifiers for.
arrays specifies a pointer to memory to store the array names.
glGenVertexArraysAPPLE(1,&myArrayObject);
2. Bind the vertex array object you want to configure.
void glBindVertexArrayAPPLE(uint array);
array is the identifier for an array that you received from glGenVertexArraysAPPLE.
glBindVertexArrayAPPLE(myArrayObject);
3. Call the pointer routines (glColorPointer and so forth.) that you would normally call inside your
rendering loop. When a vertex array object is bound, these calls change the currently bound vertex array
object instead of the default OpenGL state.
glBindBuffer(GL_ARRAY_BUFFER, staticBuffer);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, sizeof(vertexStatic),
(void*)offsetof(vertexStatic,position));
...
4. Repeat the previous steps for each configuration of vertex pointers.
5. Inside your rendering loop, replace the calls to configure the array pointers with a call to bind the vertex
array object.
glBindVertexArrayAPPLE(myArrayObject);
glDrawArrays(...);
6. If you need to get back to the default OpenGL behavior, call glBindVertexArrayAPPLE and pass in 0.
glBindVertexArrayAPPLE(0);
Best Practices for Working with Vertex Data
Vertex Array Object
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
117Textures add realism to OpenGL objects. They help objects defined by vertex data take on the material properties
of real-world objects, such as wood, brick, metal, and fur. Texture data can originate from many sources,
including images.
Many of the same techniques your application uses on vertex data can also be used to improve texture
performance.
Figure 11-1 Textures add realism to a scene
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
118
Best Practices for Working with Texture DataTextures start as pixel data that flows through an OpenGL program, as shown in Figure 11-2.
Figure 11-2 Texture data path
Rasterization
Fragment shading
and per-fragment
operations
Per-pixel
operations
Texture
assembly
Framebuffer
Vertex shading
and per-vertex
operations
Pixel data
Vertex data
The precise route that texture data takesfrom your application to itsfinal destination can impact the performance
of your application. The purpose of this chapter is to provide techniques you can use to ensure optimal
processing of texture data in your application. This chapter
●
shows how to use OpenGL extensions to optimize performance
●
lists optimal data formats and types
● provides information on working with textures whose dimensions are not a power of two
● describes creating textures from image data
●
shows how to download textures
● discusses using double buffers for texture data
Using Extensions to Improve Texture Performance
Without any optimizations, texture data flows through an OpenGL program as shown in Figure 11-3. Data from
your application first goes to the OpenGL framework, which may make a copy of the data before handing it
to the driver. If your data is not in a native format for the hardware (see “Optimal Data Formats and Types” (page
Best Practices for Working with Texture Data
Using Extensions to Improve Texture Performance
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
119128)), the driver may also make a copy of the data to convert it to a hardware-specific format for uploading to
video memory. Video memory, in turn, can keep a copy of the data. Theoretically, there could be four copies
of your texture data throughout the system.
Figure 11-3 Data copies in an OpenGL program
GPU
VRAM
OpenGL driver
OpenGL framework
Application
Data flows at different rates through the system, as shown by the size of the arrows in Figure 11-3. The fastest
data transfer happens between VRAM and the GPU. The slowest transfer occurs between the OpenGL driver
and VRAM. Data moves between the application and the OpenGL framework, and between the framework
and the driver at the same "medium" rate. Eliminating any of the data transfers, but the slowest one in particular,
will improve application performance.
There are several extensions you can use to eliminate one or more data copies and control how texture data
travels from your application to the GPU:
● GL_ARB_pixel_buffer_object allows your application to use OpenGL buffer objectsto manage texture
and image data. As with vertex buffer objects, they allow your application to hint how a buffer is used
and to decide when data is copied to OpenGL.
● GL_APPLE_client_storage allows you to prevent OpenGL from copying your texture data into the
client. Instead, OpenGL keepsthe memory pointer you provided when creating the texture. Your application
must keep the texture data at that location until the referencing OpenGL texture is deleted.
● GL_APPLE_texture_range, along with a storage hint, either GL_STORAGE_CACHED_APPLE or
GL_STORAGE_SHARED_APPLE, allows you to specify a single block of texture memory and manage it as
you see fit.
● GL_ARB_texture_rectangle provides support for non-power of-two textures.
Here are some recommendations:
Best Practices for Working with Texture Data
Using Extensions to Improve Texture Performance
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
120●
If your application requires optimal texture upload performance, use GL_APPLE_client_storage and
GL_APPLE_texture_range together to manage your textures.
●
If your application requires optimal texture download performance, use pixel buffer objects.
●
If your application requires cross-platform techniques, use pixel buffer objects for both texture uploads
and texture downloads.
● Use GL_ARB_texture_rectangle when your source images are not aligned to a power-of-2 size.
The sections that follow describe the extensions and show how to use them.
Pixel Buffer Objects
Pixel buffer objects are a core feature of OpenGL 2.1 and also available through the
GL_ARB_pixel_buffer_object extension. The procedure for setting up a pixel buffer object is almost
identical to that of vertex buffer objects.
Using Pixel Buffer Objects to Efficiently Load Textures
1. Call the function glGenBuffers to create a new name for a buffer object.
void glGenBuffers(sizei n, uint *buffers );
n is the number of buffers you wish to create identifiers for.
buffers specifies a pointer to memory to store the buffer names.
2. Call the function glBindBuffer to bind an unused name to a buffer object. After this call, the newly
created buffer object is initialized with a memory buffer of size zero and a default state. (For the default
setting, see the OpenGL specification for ARB_vertex_buffer_object.)
void glBindBuffer(GLenum target, GLuint buffer);
target should be be set to GL_PIXEL_UNPACK_BUFFER to use the buffer as the source of pixel data.
buffer specifies the unique name for the buffer object.
3. Create and initialize the data store of the buffer object by calling the function glBufferData. Essentially,
this call uploads your data to the GPU.
void glBufferData(GLenum target, sizeiptr size,
const GLvoid *data, GLenum usage);
Best Practices for Working with Texture Data
Using Extensions to Improve Texture Performance
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
121target must be set to GL_PIXEL_UNPACK_BUFFER.
size specifies the size of the data store.
*data pointsto the source data. If thisis not NULL, the source data is copied to the data store of the buffer
object. If NULL, the contents of the data store are undefined.
usage is a constant that provides a hint as to how your application plans to use the data store. For more
details on buffer hints, see “Buffer Usage Hints” (page 110)
4. Whenever you call glDrawPixels, glTexSubImage or similar functions that read pixel data from the
application, those functions use the data in the bound pixel buffer object instead.
5. To update the data in the buffer object, your application calls glMapBuffer. Mapping the buffer prevents
the GPU from operating on the data, and gives your application a pointer to memory it can use to update
the buffer.
void *glMapBuffer(GLenum target, GLenum access);
target must be set to PIXEL_UNPACK_BUFFER.
access indicatesthe operations you plan to performon the data. You can supply READ_ONLY, WRITE_ONLY,
or READ_WRITE.
6. Modify the texture data using the pointer provided by map buffer.
7. When you have finished modifying the texture, call the function glUnmapBuffer. You should
supplyPIXEL_UNPACK_BUFFER. Once the buffer is unmapped, your application can no longer access the
buffer’s data through the pointer, and the buffer’s contents are uploaded again to the GPU.
Using Pixel Buffer Objects for Asynchronous Pixel Transfers
glReadPixels normally blocks until previous commands have completed, which includes the slow process
of copying the pixel data to the application. However, if you call glReadPixels while a pixel buffer object is
bound, the function returns immediately. It does not block until you actually map the pixel buffer object to
read its content.
1. Call the function glGenBuffers to create a new name for a buffer object.
void glGenBuffers(sizei n, uint *buffers );
n is the number of buffers you wish to create identifiers for.
buffers specifies a pointer to memory to store the buffer names.
Best Practices for Working with Texture Data
Using Extensions to Improve Texture Performance
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
1222. Call the function glBindBuffer to bind an unused name to a buffer object. After this call, the newly
created buffer object is initialized with a memory buffer of size zero and a default state. (For the default
setting, see the OpenGL specification for ARB_vertex_buffer_object.)
void glBindBuffer(GLenum target, GLuint buffer);
target should be be set to GL_PIXEL_PACK_BUFFER to use the buffer as the destination for pixel data.
buffer specifies the unique name for the buffer object.
3. Create and initialize the data store of the buffer object by calling the function glBufferData.
void glBufferData(GLenum target, sizeiptr size,
const GLvoid *data, GLenum usage);
target must be set to GL_ARRAY_BUFFER.
size specifies the size of the data store.
*data pointsto the source data. If thisis not NULL, the source data is copied to the data store of the buffer
object. If NULL, the contents of the data store are undefined.
usage is a constant that provides a hint as to how your application plans to use the data store. For more
details on buffer hints, see “Buffer Usage Hints” (page 110)
4. Call glReadPixels or a similar function. The function inserts a command to read the pixel data into the
bound pixel buffer object and then returns.
5. To take advantage of asynchronous pixel reads, your application should perform other work.
6. To retrieve the data in the pixel buffer object, your application calls glMapBuffer. This blocks OpenGL
until the previously queued glReadPixels command completes, maps the data, and provides a pointer
to your application.
void *glMapBuffer(GLenum target, GLenum access);
target must be set to GL_PIXEL_PACK_BUFFER.
access indicatesthe operations you plan to performon the data. You can supply READ_ONLY, WRITE_ONLY,
or READ_WRITE.
7. Write vertex data to the pointer provided by map buffer.
8. When you no longer need the vertex data, call the function glUnmapBuffer. You should supply
GL_PIXEL_PACK_BUFFER. Once the buffer is unmapped, the data is no longer accessible to your
application.
Best Practices for Working with Texture Data
Using Extensions to Improve Texture Performance
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
123Using Pixel Buffer Objects to Keep Data on the GPU
There is no difference between a vertex buffer object and a pixel buffer object except for the target to which
they are bound. An application can take the results in one buffer and use them as another buffer type. For
example, you could use the pixel resultsfrom a fragmentshader and reinterpret them as vertex data in a future
pass, without ever leaving the GPU:
1. Set up your first pass and submit your drawing commands.
2. Bind a pixel buffer object and call glReadPixels to fetch the intermediate results into a buffer.
3. Bind the same buffer as a vertex buffer.
4. Set up the second pass of your algorithm and submit your drawing commands.
Keeping your intermediate data inside the GPU when performing multiple passes can result in great performance
increases.
Apple Client Storage
The Apple client storage extension (APPLE_client_storage) lets you provide OpenGL with a pointer to
memory that your application allocates and maintains. OpenGL retains a pointer to your data but does not
copy the data. Because OpenGL references your data, your application must retain its copy of the data until
all referencing textures are deleted. By using this extension you can eliminate the OpenGL framework copy as
shown in Figure 11-4. Note that a texture width must be a multiple of 32 bytes for OpenGL to bypass the copy
operation from the application to the OpenGL framework.
Figure 11-4 The client storage extension eliminates a data copy
GPU
VRAM
OpenGL driver
OpenGL framework
Application
The Apple clientstorage extension defines a pixelstorage parameter, GL_UNPACK_CLIENT_STORAGE_APPLE,
that you pass to the OpenGL function glPixelStorei to specify that your application retains storage for
textures. The following code sets up client storage:
Best Practices for Working with Texture Data
Using Extensions to Improve Texture Performance
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
124glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
For detailed information, see the OpenGL specification for the Apple client storage extension.
Apple Texture Range and Rectangle Texture
The Apple texture range extension (APPLE_texture_range) lets you define a region of memory used for
texture data. Typically you specify an address range that encompasses the storage for a set of textures. This
allows the OpenGL driver to optimize memory usage by creating a single memory mapping for all of the
textures. You can also provide a hint as to how the data should be stored: cached or shared. The cached hint
specifies to cache texture data in video memory. This hint is recommended when you have textures that you
plan to use multiple times or that use linear filtering. The shared hint indicates that data should be mapped
into a region of memory that enables the GPU to access the texture data directly (via DMA) without the need
to copy it. This hint is best when you are using large images only once, perform nearest-neighbor filtering, or
need to scale down the size of an image.
The texture range extension defines the following routine for making a single memory mapping for all of the
textures used by your application:
void glTextureRangeAPPLE(GLenum target, GLsizei length, GLvoid *pointer);
target is a valid texture target, such as GL_TEXTURE_2D.
length specifies the number of bytes in the address space referred to by the pointer parameter.
*pointer points to the address space that your application provides for texture storage.
You provide the hint parameter and a parameter value to to the OpenGL function glTexParameteri. The
possible values for the storage hint parameter (GL_TEXTURE_STORAGE_HINT_APPLE) are
GL_STORAGE_CACHED_APPLE or GL_STORAGE_SHARED_APPLE.
Some hardware requires texture dimensions to be a power-of-two before the hardware can upload the data
using DMA. The rectangle texture extension (ARB_texture_rectangle) was introduced to allow texture
targets for textures of any dimensions—that is, rectangle textures (GL_TEXTURE_RECTANGLE_ARB). You need
to use the rectangle texture extension together with the Apple texture range extension to ensure OpenGL
uses DMA to access your texture data. These extensions allow you to bypass the OpenGL driver, as shown in
Figure 11-5.
Best Practices for Working with Texture Data
Using Extensions to Improve Texture Performance
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
125Note that OpenGL does not use DMA for a power-of-two texture target (GL_TEXTURE_2D). So, unlike the
rectangular texture, the power-of-two texture will incur one additional copy and performance won't be quite
as fast. The performance typically isn't an issue because games, which are the applications most likely to use
power-of-two textures, load textures at the start of a game or level and don't upload textures in real time as
often as applications that use rectangular textures, which usually play video or display images.
The next section has code examples that use the texture range and rectangle textures together with the Apple
client storage extension.
Figure 11-5 The texture range extension eliminates a data copy
GPU
VRAM
OpenGL driver
OpenGL framework
Application
For detailed information on these extensions,see the OpenGL specification for the Apple texture range extension
and the OpenGL specification for the ARB texture rectangle extension.
Best Practices for Working with Texture Data
Using Extensions to Improve Texture Performance
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
126Combining Client Storage with Texture Ranges
You can use the Apple client storage extension along with the Apple texture range extension to streamline
the texture data path in your application. When used together, OpenGL moves texture data directly into video
memory, as shown in Figure 11-6. The GPU directly accesses your data (via DMA). The set up is slightly different
for rectangular and power-of-two textures. The code examples in this section upload textures to the GPU. You
can also use these extensions to download textures, see “Downloading Texture Data” (page 136).
Figure 11-6 Combining extensions to eliminate data copies
GPU
VRAM
OpenGL driver
OpenGL framework
Application
Listing 11-1 shows how to use the extensions for a rectangular texture. After enabling the texture rectangle
extension you need to bind the rectangular texture to a target. Next, set up the storage hint. Call
glPixelStorei to set up the Apple client storage extension. Finally, call the function glTexImage2D with
a with a rectangular texture target and a pointer to your texture data.
Note: The texture rectangle extension limits what can be done with rectangular textures. To
understand the limitationsin detail, read the OpenGL extension for texture rectangles. See “Working
with Non–Power-of-Two Textures” (page 129) for an overview of the limitations and an alternative
to using this extension.
Listing 11-1 Using texture extensions for a rectangular texture
glEnable (GL_TEXTURE_RECTANGLE_ARB);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, id);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_STORAGE_HINT_APPLE,
GL_STORAGE_CACHED_APPLE);
glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
Best Practices for Working with Texture Data
Using Extensions to Improve Texture Performance
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
127glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,
0, GL_RGBA, sizex, sizey, 0, GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV,
myImagePtr);
Setting up a power-of-two texture to use these extensions is similar to what's needed to set up a rectangular
texture, as you can see by looking at Listing 11-2. The difference is that the GL_TEXTURE_2D texture target
replaces the GL_TEXTURE_RECTANGLE_ARB texture target.
Listing 11-2 Using texture extensions for a power-of-two texture
glBindTexture(GL_TEXTURE_2D, myTextureName);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_STORAGE_HINT_APPLE,
GL_STORAGE_CACHED_APPLE);
glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
sizex, sizey, 0, GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV, myImagePtr);
Optimal Data Formats and Types
The best format and data type combinations to use for texture data are:
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV
GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV)
GL_YCBCR_422_APPLE, GL_UNSIGNED_SHORT_8_8_REV_APPLE
The combination GL_RGBA and GL_UNSIGNED_BYTE needs to be swizzled by many cards when the data is
loaded, so it's not recommended.
Best Practices for Working with Texture Data
Optimal Data Formats and Types
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
128Working with Non–Power-of-Two Textures
OpenGL is often used to process video and images, which typically have dimensionsthat are not a power-of-two.
Until OpenGL 2.0, the texture rectangle extension (ARB_texture_rectangle) provided the only option for
a rectangular texture target. This extension, however, imposesthe following restrictions on rectangular textures:
● You can't use mipmap filtering with them.
● You can use only these wrap modes: GL_CLAMP, GL_CLAMP_TO_EDGE, and GL_CLAMP_TO_BORDER.
● The texture cannot have a border.
● The texture uses non-normalized texture coordinates. (See Figure 11-7.)
OpenGL 2.0 adds another option for a rectangular texture target through the
ARB_texture_non_power_of_two extension, which supports these textures without the limitations of the
ARB_texture_rectangle extension. Before using it, you must check to make sure the functionality is available.
You'll also want to consult the OpenGL specification for the non—power-of-two extension.
Figure 11-7 Normalized and non-normalized coordinates
Normalized Non-normalized
0 1
1
0 Width
Height
If your code runs on a system that does not support either the ARB_texture_rectangle or
ARB_texture_non_power_of_two extensions you have these options for working with with rectangular
images:
● Use the OpenGL function gluScaleImage to scale the image so that it fitsin a rectangle whose dimensions
are a power of two. The image undoes the scaling effect when you draw the image from the properly
sized rectangle back into a polygon that has the correct aspect ratio for the image.
Note: This option can result in the loss of some data. But if your application runs on hardware
that doesn'tsupport the ARB_texture_rectangle extension, you may need to use this option.
Best Practices for Working with Texture Data
Working with Non–Power-of-Two Textures
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
129● Segment the image into power-of-two rectangles, as shown in Figure 11-8 by using one image buffer and
different texture pointers. Notice how the sides and corners of the image shown in Figure 11-8 are
segmented into increasingly smaller rectangles to ensure that every rectangle has dimensions that are a
power of two. Special care may be needed at the borders between each segment to avoid filtering artifacts
if the texture is scaled or rotated.
Figure 11-8 An image segmented into power-of-two tiles
Best Practices for Working with Texture Data
Working with Non–Power-of-Two Textures
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
130Creating Textures from Image Data
OpenGL on the Macintosh provides several options for creating high-quality textures from image data. OS X
supports floating-point pixel values, multiple image file formats, and a variety of color spaces. You can import
a floating-point image into a floating-point texture. Figure 11-9 shows an image used to texture a cube.
Figure 11-9 Using an image as a texture for a cube
For Cocoa, you need to provide a bitmap representation. You can create an NSBitmapImageRep object from
the contents of an NSView object. You can use the Image I/O framework (see CGImageSource Reference ). This
framework has support for many different file formats, floating-point data, and a variety of color spaces.
Furthermore, it is easy to use. You can import image data as a texture simply by supplying a CFURL object that
specifies the location of the texture. There is no need for you to convert the image to an intermediate integer
RGB format.
Creating a Texture from a Cocoa View
You can use the NSView class or a subclass of it for texturing in OpenGL. The process is to first store the image
data from an NSView object in an NSBitmapImageRep object so that the image data is in a format that can
be readily used as texture data by OpenGL. Then, after setting up the texture target, you supply the bitmap
data to the OpenGL function glTexImage2D. Note that you must have a valid, current OpenGL context set
up.
Best Practices for Working with Texture Data
Creating Textures from Image Data
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
131Note: You can't create an OpenGL texture from image data that's provided by a view created from
the following classes: NSProgressIndicator, NSMovieView, and NSOpenGLView. Thisis because
these views do not use the window backing store, which is what the method
initWithFocusedViewRect: reads from.
Listing 11-3 shows a routine that uses this process to create a texture from the contents of an NSView object.
A detailed explanation for each numbered line of code appears following the listing.
Listing 11-3 Building an OpenGL texture from an NSView object
-(void)myTextureFromView:(NSView*)theView
textureName:(GLuint*)texName
{
NSBitmapImageRep * bitmap = [theView bitmapImageRepForCachingDisplayInRect:
[theView visibleRect]]; // 1
int samplesPerPixel = 0;
[theView cacheDisplayInRect:[theView visibleRect] toBitmapImageRep:bitmap];
// 2
samplesPerPixel = [bitmap samplesPerPixel]; // 3
glPixelStorei(GL_UNPACK_ROW_LENGTH, [bitmap bytesPerRow]/samplesPerPixel); // 4
glPixelStorei (GL_UNPACK_ALIGNMENT, 1); // 5
if (*texName == 0) // 6
glGenTextures (1, texName);
glBindTexture (GL_TEXTURE_RECTANGLE_ARB, *texName); // 7
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_MIN_FILTER, GL_LINEAR); // 8
if(![bitmap isPlanar] &&
(samplesPerPixel == 3 || samplesPerPixel == 4)) { // 9
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,
0,
samplesPerPixel == 4 ? GL_RGBA8 : GL_RGB8,
[bitmap pixelsWide],
[bitmap pixelsHigh],
0,
Best Practices for Working with Texture Data
Creating Textures from Image Data
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
132samplesPerPixel == 4 ? GL_RGBA : GL_RGB,
GL_UNSIGNED_BYTE,
[bitmap bitmapData]);
} else {
// Your code to report unsupported bitmap data
}
}
Here's what the code does:
1. Allocates an NSBitmapImageRep object.
2. Initializes the NSBitmapImageRep object with bitmap data from the current view.
3. Gets the number of samples per pixel.
4. Sets the appropriate unpacking row length for the bitmap.
5. Sets the byte-aligned unpacking that's needed for bitmaps that are 3 bytes per pixel.
6. If a texture object is not passed in, generates a new texture object.
7. Binds the texture name to the texture target.
8. Sets filtering so that it does not use a mipmap, which would be redundant for the texture rectangle
extension.
9. Checks to see if the bitmap is nonplanar and is either a 24-bit RGB bitmap or a 32-bit RGBA bitmap. If so,
retrievesthe pixel data using the bitmapData method, passing it along with other appropriate parameters
to the OpenGL function for specifying a 2D texture image.
Creating a Texture from a Quartz Image Source
Quartz images (CGImageRef data type) are defined in the Core Graphics framework
(ApplicationServices/CoreGraphics.framework/CGImage.h) while the image source data type for
reading image data and creating Quartz images from an image source is declared in the Image I/O framework
(ApplicationServices/ImageIO.framework/CGImageSource.h). Quartz provides routines that read a
wide variety of image data.
To use a Quartz image as a texture source, follow these steps:
1. Create a Quartz image source by supplying a CFURL object to the function
CGImageSourceCreateWithURL.
2. Create a Quartz image by extracting an image from the image source, using the function
CGImageSourceCreateImageAtIndex.
Best Practices for Working with Texture Data
Creating Textures from Image Data
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
1333. Extract the image dimensions using the function CGImageGetWidth and CGImageGetHeight. You'll
need these to calculate the storage required for the texture.
4. Allocate storage for the texture.
5. Create a color space for the image data.
6. Create a Quartz bitmap graphics context for drawing. Make sure to set up the context for pre-multiplied
alpha.
7. Draw the image to the bitmap context.
8. Release the bitmap context.
9. Set the pixel storage mode by calling the function glPixelStorei.
10. Create and bind the texture.
11. Set up the appropriate texture parameters.
12. Call glTexImage2D, supplying the image data.
13. Free the image data.
Listing 11-4 shows a code fragment that performsthese steps. Note that you must have a valid, current OpenGL
context.
Listing 11-4 Using a Quartz image as a texture source
CGImageSourceRef myImageSourceRef = CGImageSourceCreateWithURL(url, NULL);
CGImageRef myImageRef = CGImageSourceCreateImageAtIndex (myImageSourceRef, 0,
NULL);
GLint myTextureName;
size_t width = CGImageGetWidth(myImageRef);
size_t height = CGImageGetHeight(myImageRef);
CGRect rect = {{0, 0}, {width, height}};
void * myData = calloc(width * 4, height);
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGContextRef myBitmapContext = CGBitmapContextCreate (myData,
width, height, 8,
width*4, space,
kCGBitmapByteOrder32Host |
kCGImageAlphaPremultipliedFirst);
CGContextSetBlendMode(myBitmapContext, kCGBlendModeCopy);
CGContextDrawImage(myBitmapContext, rect, myImageRef);
Best Practices for Working with Texture Data
Creating Textures from Image Data
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
134CGContextRelease(myBitmapContext);
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, &myTextureName);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, myTextureName);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, width, height,
0, GL_BGRA_EXT, GL_UNSIGNED_INT_8_8_8_8_REV, myData);
free(myData);
For more information on using Quartz, see Quartz 2D Programming Guide , CGImage Reference , and
CGImageSource Reference .
Getting Decompressed Raw Pixel Data from a Source Image
You can use the Image I/O framework together with a Quartz data provider to obtain decompressed raw pixel
data from a source image, as shown in Listing 11-5. You can then use the pixel data for your OpenGL texture.
The data has the same format as the source image, so you need to make sure that you use a source image that
has the layout you need.
Alpha is not premultiplied for the pixel data obtained in Listing 11-5, but alpha is premultiplied for the pixel
data you get when using the code described in “Creating a Texture from a Cocoa View” (page 131) and “Creating
a Texture from a Quartz Image Source” (page 133).
Listing 11-5 Getting pixel data from a source image
CGImageSourceRef myImageSourceRef = CGImageSourceCreateWithURL(url, NULL);
CGImageRef myImageRef = CGImageSourceCreateImageAtIndex (myImageSourceRef, 0,
NULL);
CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(myImageRef));
void *pixelData = CFDataGetBytePtr(data);
Best Practices for Working with Texture Data
Creating Textures from Image Data
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
135Downloading Texture Data
A texture download operation uses the same data path as an upload operation except that the data path is
reversed. Downloading transfers texture data, using direct memory access (DMA), from VRAM into a texture
that can then be accessed directly by your application. You can use the Apple client range, texture range, and
texture rectangle extensions for downloading, just as you would for uploading.
To download texture data using the Apple client storage, texture range, and texture rectangle extensions:
● Bind a texture name to a texture target.
● Set up the extensions
● Call the function glCopyTexSubImage2D to copy a texture subimage from the specified window
coordinates. This call initiates an asynchronous DMA transfer to system memory the next time you call a
flush routine. The CPU doesn't wait for this call to complete.
● Call the function glGetTexImage to transfer the texture into system memory. Note that the parameters
must match the ones that you used to set up the texture when you called the function glTexImage2D.
This call is the synchronization point; it waits until the transfer is finished.
Listing 11-6 shows a code fragment that downloads a rectangular texture that uses cached memory. Your
application processes data between the glCopyTexSubImage2D and glGetTexImage calls. How much
processing? Enough so that your application does not need to wait for the GPU.
Listing 11-6 Code that downloads texture data
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, myTextureName);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_STORAGE_HINT_APPLE,
GL_STORAGE_SHARED_APPLE);
glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA,
sizex, sizey, 0, GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV, myImagePtr);
glCopyTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB,
0, 0, 0, 0, 0, image_width, image_height);
glFlush();
// Do other work processing here, using a double or triple buffer
glGetTexImage(GL_TEXTURE_RECTANGLE_ARB, 0, GL_BGRA,
Best Practices for Working with Texture Data
Downloading Texture Data
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
136GL_UNSIGNED_INT_8_8_8_8_REV, pixels);
Double Buffering Texture Data
When you use any technique that allowsthe GPU to access your texture data directly,such asthe texture range
extension, it's possible for the GPU and CPU to access the data at the same time. To avoid such a collision, you
must synchronize the GPU and the CPU. The simplest way is shown in Figure 11-10. Your application works
on the data, flushes it to the GPU and waits until the GPU is finished before working on the data again.
One technique for ensuring that the GPU is finished executing commands before your application sends more
data is to insert a token into the command stream and use that to determine when the CPU can touch the
data again, as described in “Use Fences for Finer-Grained Synchronization” (page 98). Figure 11-10 uses the
fence extension command glFinishObject to synchronize buffer updates for a stream of single-buffered
texture data. Notice that when the CPU is processing texture data, the GPU is idle. Similarly, when the GPU is
processing texture data, the CPU is idle. It's much more efficient for the GPU and CPU to work asynchronously
than to work synchronously. Double buffering data is a technique that allows you to process data asynchronously,
as shown in Figure 11-11 (page 138).
Figure
11-10
Single-buffered data
CPU
GPU
glFinishObject(..., 1) glFinishObject(..., 1)
TIME Frame 1 Frame 2
glFlush glFlush
Best Practices for Working with Texture Data
Double Buffering Texture Data
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
137To double buffer data, you must supply two sets of data to work on. Note in Figure 11-11 that while the GPU
is rendering one frame of data, the CPU processes the next. After the initial startup, neither processing unit is
idle. Using the glFinishObject function provided by the fence extension ensures that buffer updating is
synchronized.
Figure
11-11
Double-buffered data
CPU
GPU
glFinishObject(..., 1) glFinishObject(..., 1)
glFinishObject(..., 2) glFinishObject(..., 2)
Time Frame 1 Frame 2 Frame 3 Frame 4
glFlush glFlush glFlush glFlush
Best Practices for Working with Texture Data
Double Buffering Texture Data
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
138OpenGL 1.x used fixed functions to deliver a useful graphics pipeline to application developers. To configure
the various stages of the pipeline shown in Figure 12-1, applications called OpenGL functions to tweak the
calculationsthat were performed for each vertex and fragment. Complex algorithmsrequired multiple rendering
passes and dozens of function calls to configure the calculations that the programmer desired. Extensions
offered new configuration options, but did not change the complex nature of OpenGL programming.
Figure 12-1 OpenGL fixed-function pipeline
Geometry
Fragment
Framebuffer operations
Texturing
Fog
Alpha, stencil, and depth tests
Framebuffer blending
Primitive assembly
Clipping
Vertex
Application Primitives and image data
Transform and lighting
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
139
Customizing the OpenGL Pipeline with ShadersStarting with OpenGL 2.0, some stages of the OpenGL pipeline can be replaced with shaders. A shader is a
program written in a special shading language. This program is compiled by OpenGL and uploaded directly
into the graphics hardware. Figure 12-2 shows where your applications can hook into the pipeline with shaders.
Figure 12-2 OpenGL shader pipeline
Geometry
Fragment
Framebuffer operations
Fragment shaders
Alpha, stencil, and depth tests
Framebuffer blending
Geometry shaders
Clipping
Vertex
Application Primitives and image data
Vertex shaders
Shaders offer a considerable number of advantages to your application:
● Shaders give you precise control over the operations that are performed to render your images.
● Shaders allow for algorithmsto be written in a terse, expressive format. Rather than writing complex blocks
of configuration callsto implement a mathematical operation, you write code that expressesthe algorithm
directly.
● Older graphics processors implemented the fixed-function pipeline in hardware or microcode, but now
graphics processors are general-purpose computing devices. The fixed function pipeline is itself
implemented as a shader.
● Shaders allow for longer and more complex algorithms to be implemented using a single rendering pass.
Because you have extensive control over the pipeline, it is also easier to implement multipass algorithms
without requiring the data to be read back from the GPU.
● Your application can switch between different shaders with a single function call. In contrast, configuring
the fixed-function pipeline incurs significant function-call overhead.
If your application uses the fixed-function pipeline, a critical task is to replace those tasks with shaders.
Customizing the OpenGL Pipeline with Shaders
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
140If you are new to shaders, OpenGL Shading Language , by Randi J. Rost, is an excellent guide for those looking
to learn more about writing shaders and integrating them into your application. The rest of this chapter provides
some boilerplate code, briefly describe the extensions that implement shaders, and discusses tools that Apple
provides to assist you in writing shaders.
Shader Basics
OpenGL 2.0 offers vertex and fragmentshaders, to take over the processing of those two stages of the graphics
pipeline. These same capabilities are also offered by the ARB_shader_objects, ARB_vertex_shader and
ARB_fragment_shaderextensions. Vertex shading is available on all hardware running OS X v10.5 or later.
Fragment shading is available on all hardware running OS X v10.6 and the majority of hardware running OS
X v10.5.
Creating a shader program is an expensive operation compared to other OpenGL state changes. Listing 12-1
presents a typical strategy to load, compile, and verify a shader program.
Listing 12-1 Loading a Shader
/** Initialization-time for shader **/
GLuint shader, prog;
GLchar *shaderText = "... shader text ...";
// Create ID for shader
shader = glCreateShader(GL_VERTEX_SHADER);
// Define shader text
glShaderSource(shaderText);
// Compile shader
glCompileShader(shader);
// Associate shader with program
glAttachShader(prog, shader);
// Link program
glLinkProgram(prog);
// Validate program
glValidateProgram(prog);
// Check the status of the compile/link
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLen);
if(logLen > 0)
{
// Show any errors as appropriate
glGetProgramInfoLog(prog, logLen, &logLen, log);
Customizing the OpenGL Pipeline with Shaders
Shader Basics
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
141fprintf(stderr, "Prog Info Log: %s\n", log);
}
// Retrieve all uniform locations that are determined during link phase
for(i = 0; i < uniformCt; i++)
{
uniformLoc[i] = glGetUniformLocation(prog, uniformName);
}
// Retrieve all attrib locations that are determined during link phase
for(i = 0; i < attribCt; i++)
{
attribLoc[i] = glGetAttribLocation(prog, attribName);
}
/** Render stage for shaders **/
glUseProgram(prog);
This code loads the text source for a vertex shader, compiles it, and adds it to the program. A more complex
example might also attach fragment and geometry shaders. The program islinked and validated for correctness.
Finally, the program retrieves information about the inputs to the shader and stores then in its own arrays.
When the application is ready to use the shader, it calls glUseProgram to make it the current shader.
For best performance, your application should create shaders when your application is initialized, and not
inside the rendering loop. Inside your rendering loop, you can quickly switch in the appropriate shaders by
calling glUseProgram. For best performance, use the vertex array object extension to also switch in the vertex
pointers. See “Vertex Array Object” (page 116) for more information.
Advanced Shading Extensions
In addition to the standard shader,some Macs offer additionalshading extensionsto reveal advanced hardware
capabilities. Not all of these extensions are available on all hardware,so you need to assess whether the features
of each extension are worth implementing in your application.
Transform Feedback
The EXT_transform_feedback extension is available on all hardware running OS X v10.5 or later. With the
feedback extension, you can capture the results of the vertex shader into a buffer object, which can be used
as an input to future commands. This is similar to the pixel buffer object technique described in “Using Pixel
Buffer Objects to Keep Data on the GPU” (page 124), but more directly captures the results you desire.
Customizing the OpenGL Pipeline with Shaders
Advanced Shading Extensions
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
142GPU Shader 4
The EXT_gpu_shader4 extension extends the OpenGL shading language to offer new operations, including:
● Full integer support.
● Built-in shader variable to reference the current vertex.
● Built-in shader variable to reference the current primitive. This makes it easier to use a shader to use the
same static vertex data to render multiple primitives, using a shader and uniform variables to customize
each instance of that primitive.
● Unfiltered texture fetches using integer coordinates.
● Querying the size of a texture within a shader.
● Offset texture lookups.
● Explicit gradient and LOD texture lookups.
● Depth Cubemaps.
Geometry Shaders
The EXT_geometry_shader4 extension allows your create geometry shaders. A geometry shader accepts
transformed vertices and can add or remove vertices before passing them down to the rasterizer. This allows
the application to add or remove geometry based on the calculated values in the vertex. For example, given
a triangle and its neighboring vertices, your application could emit additional vertices to better create a more
accurate appearance of a curved surface.
Uniform Buffers
The EXT_bindable_uniform extension allows your application to allocate buffer objects and use them as the
source for uniform data in your shaders. Instead of relying on a single block of uniform memory supplied by
OpenGL, your application allocates buffer objects using the same API that it uses to implement vertex buffer
objects (“Vertex Buffers” (page 107)). Instead of making a function call for each uniform variable you want to
change, you can swap all of the uniform data by binding to a different uniform buffer.
Customizing the OpenGL Pipeline with Shaders
Advanced Shading Extensions
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
143Aliasing is the bane of the digital domain. In the early days of the personal computer, jagged edges and blocky
graphics were accepted by the user simply because not much could be done to correct them. Now with faster
hardware and higher-resolution displays, there are several antialiasing techniques that can smooth edges to
achieve a more realistic scene.
OpenGL supports antialiasing that operates at the level of lines and polygons as well as at the level of the full
scene. This chapter discusses techniques for full scene antialiasing (FSAA). If your application needs point or
line antialiasing instead of full scene antialiasing, use the built in OpenGL point and line antialiasing functions.
These are described in Section 3.4.2 in the OpenGL Specification.
The three antialiasing techniques in use today are multisampling, supersampling, and alpha channel blending:
● Multisampling defines a technique for sampling pixel content at multiple locations for each pixel. This is
a good technique to use if you want to smooth polygon edges.
● Supersampling renders at a much higher resolution than what's needed for the display. Prior to drawing
the content to the display, OpenGL scales and filters the content to the appropriate resolution. This is a
good technique to use when you want to smooth texture interiors in addition to polygon edges.
● Alpha channel blending uses the alpha value of a fragment to control how to blend the fragment with
the pixel values that are already in the framebuffer. It's a good technique to use when you want to ensure
that foreground and background images are composited smoothly.
The ARB_multisample extension defines a specification for full scene antialiasing. It describes multisampling
and alpha channel sampling. The specification does not specifically mention supersampling but its wording
doesn't preclude supersampling. The antialiasing methods that are available depend on the hardware and the
actual implementation depends on the vendor. Some graphics cards support antialiasing using a mixture of
multisampling and supersampling. The methodology used to select the samples can vary as well. Your best
approach is to query the renderer to find out exactly what is supported. OpenGL lets you provide a hint to the
renderer asto which antialiasing technique you prefer. Hints are available asrenderer attributesthat you supply
when you create a pixel format object.
A smallersubset of rendererssupport the EXT_framebuffer_blit and EXT_framebuffer_multisample extensions.
These extensions allow your application to create multisampled offscreen frame buffer objects, render detailed
scenesto them, with precise control over when the multisampled renderbuffer isresolved to a single displayable
color per pixel.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
144
Techniques for Scene AntialiasingGuidelines
Keep the following in mind when you set up full scene antialiasing:
● Although a system may have enough VRAM to accommodate a multisample buffer, a large buffer can
affect the ability of OpenGL to maintain a properly working texture set. Keep in mind that the buffers
associated with the rendering context—depth and stencil—increase in size by a factor equal to number
of samples per pixel.
● The OpenGL driver allocates the memory needed for the multisample buffer; your application should not
allocate this memory.
● Any antialiasing algorithm that operates on the full scene requires additional computing resources. There
is a tradeoff between performance and quality. For that reason, you may want to provide a user interface
that allows the user to enable and disable FSAA, or to choose the level of quality for antialiasing.
● The commands glEnable(GL_MULTISAMPLE) and glDisable(GL_MULTISAMPLE) are ignored on
some hardware because some graphics cards have the feature enabled all the time. That doesn't mean
that you should not call these commands because you'll certainly need them on hardware that doesn't
ignore them.
● A hint as to the variant of sampling you want is a suggestion, not a command. Not all hardware supports
all types of antialiasing. Other hardware mixes multisampling with supersampling techniques. The driver
dictates the type of antialiasing that's actually used in your application.
● The best way to find out which sample modes are supported is to call the CGL function
CGLDescribeRenderer with the renderer property kCGLRPSampleModes or kCGLRPSampleAlpha.
You can also determine how many samples the renderer supports by calling CGLDescribeRenderer
with the renderer property kCGLRPMaxSamples.
General Approach
The general approach to setting up full scene antialiasing is as follows:
1. Check to see what's supported. Not all renderers support the ARB multisample extension, so you need to
check for this functionality (see “Detecting Functionality” (page 83)).
To find out what type of antialiasing a specific renderersupports, call the function CGLDescribeRenderer.
Supply the renderer property kCGLRPSampleModes to find out whether the renderer supports
multisampling and supersampling. Supply kCGLRPSampleAlpha to see whether the renderer supports
alpha sampling.
Techniques for Scene Antialiasing
Guidelines
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
145You can choose to exclude unsupported hardware from the pixel format search by specifying only the
hardware thatsupports multisample antialiasing. Keep in mind that if you exclude unsupported hardware,
the unsupported displays will not render anything. If you include unsupported hardware, OpenGL uses
normal aliased rendering to the unsupported displays and multisampled rendering to supported displays.
2. Include these buffer attributes in the attributes array:
● The appropriate sample buffer attribute constant (NSOpenGLPFASampleBuffers or
kCGLPFASampleBuffers) along with the number of multisample buffers. At thistime the specification
allows only one multisample buffer.
● The appropriate samples constant (NSOpenGLPFASamples or kCGLPFASamples) along with the
number ofsamples per pixel. You can supply 2, 4, 6, or more depending on what the renderersupports
and the amount of VRAM available. The value that you supply affects the quality, memory use, and
speed of the multisampling operation. For fastest performance, and to use the least amount of video
memory, specify 2 samples. When you need more quality, specify 4 or more.
● The no recovery attribute ( NSOpenGLPFANoRecovery or kCGLPFANoRecovery). Although enabling
this attribute is not mandatory, it's recommended to prevent OpenGL from using software fallback
as a renderer. Multisampled antialiasing performance is slow in the software renderer.
3. Optionally provide a hint for the type of antialiasing you want—multisampling, supersampling, or alpha
sampling. See “Hinting for a Specific Antialiasing Technique” (page 147).
4. Enable multisampling with the following command:
glEnable(GL_MULTISAMPLE);
Regardless of the enabled state, OpenGL always uses the multisample buffer if you supply the appropriate
buffer attributes when you set up the pixel format object. If you haven'tsupplied the appropriate attributes,
enabling multisampling has no effect.
When multisampling is disabled, all coverage values are set to 1, which gives the appearance of rendering
without multisampling.
Some graphics hardware leaves multisampling enabled all the time. However, don't rely on hardware to
have multisampling enabled; use glEnable to programmatically turn on this feature.
5. Optionally provide hints for the rendering algorithm. You perform this optional step only if you want
OpenGL to compute coverage values by a method other than uniformly weighting samples and averaging
them.
Some hardware supports a multisample filter hint through an OpenGL
extension—GL_NV_multisample_filter_hint. This hint allows an OpenGL implementation to use
an alternative method of resolving the color of multisampled pixels.
Techniques for Scene Antialiasing
General Approach
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
146You can specify that OpenGL usesfaster or nicer rendering by calling the OpenGL function glHint, passing
the constant GL_MULTISAMPLE_FILTER_HINT_NV asthe target parameter and GL_FASTEST or GL_NICEST
as the mode parameter. Hints allow the hardware to optimize the output if it can. There is no performance
penalty or returned error for issuing a hint that's not supported.
For more information, see the OpenGL extension registry for NV_multisample_filter_hint.
Hinting for a Specific Antialiasing Technique
When you set up your renderer and buffer attributes for full scene antialiasing, you can specify a hint to prefer
one antialiasing technique over the others. If the underlying renderer does not have sufficient resources to
support what you request, OpenGL ignores the hint. If you do not supply the appropriate buffer attributes
when you create a pixel format object, then the hint does nothing. Table 13-1 lists the hinting constants
available for the NSOpenGLPixelFormat class and CGL.
Table 13-1 Antialiasing hints
Multisampling Supersampling Alpha blending
NSOpenGLPFAMultisample NSOpenGLPFASupersample NSOpenGLPFASampleAlpha
kCGLPFAMultisample kCGLPFASupersample kCGLPFASampleAlpha
Techniques for Scene Antialiasing
Hinting for a Specific Antialiasing Technique
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
147Concurrency is the notion of multiple things happening at the same time. In the context of computers,
concurrency usually refers to executing tasks on more than one processor at the same time. By performing
work in parallel, tasks complete sooner, and applications become more responsive to the user. The good news
isthat well-designed OpenGL applications already exhibit a specific form of concurrency—concurrency between
application processing on the CPU and OpenGL processing on the GPU. Many of the techniques introduced
in “OpenGL Application Design Strategies” (page 89) are aimed specifically at creating OpenGL applications
that exhibit great CPU-GPU parallelism. However, modern computers not only contain a powerful GPU, but
also contain multiple CPUs. Sometimesthose CPUs have multiple cores, each capable of performing calculations
independently of the others. It is critical that applications be designed to take advantage of concurrency where
possible. Designing a concurrent application means decomposing the work your application performs into
subtasks and identifying which tasks can safely operate in parallel and which tasks must be executed
sequentially—that is, which tasks are dependent on either resources used by other tasks or results returned
from those tasks.
Each process in OS X is made up of one or more threads. A thread is a stream of execution that runs code for
the process. Multicore systems offer true concurrency by allowing multiple threads to execute simultaneously.
Apple offers both traditional threads and a feature called Grand CentralDispatch (GCD). Grand Central Dispatch
allows you to decompose your application into smaller tasks without requiring the application to manage
threads. GCD allocates threads based on the number of cores available on the system and automatically
schedules tasks to those threads.
At a higher level, Cocoa offers NSOperation and NSOperationQueue to provide an Objective-C abstraction
for creating and scheduling units of work. On OS X v10.6, operation queues use GCD to dispatch work; on OS
X v10.5, operation queues create threads to execute your application’s tasks.
This chapter does not attempt describe these technologiesin detail. Before you consider how to add concurrency
to your OpenGL application, you should first readConcurrency Programming Guide . If you plan on managing
threads manually, you should also read Threading Programming Guide . Regardless of which technique you
use, there are additional restrictions when calling OpenGL on multithreaded systems. This chapter helps you
understand when multithreading improves your OpenGL application’s performance, the restrictions OpenGL
places on multithreaded applications, and common design strategies you might use to implement concurrency
in an OpenGL application. Some of these design techniques can get you an improvement in just a few lines
of code.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
148
Concurrency and OpenGLIdentifying Whether an OpenGL Application Can Benefit from
Concurrency
Creating a multithreaded application requires significant effort in the design, implementation, and testing of
your application. Threads also add complexity and overhead to an application. For example, your application
may need to copy data so that it can be handed to a worker thread, or multiple threads may need to synchronize
access to the same resources. Before you attempt to implement concurrency in an OpenGL application, you
should optimize your OpenGL code in a single-threaded environment using the techniques described in
“OpenGL Application Design Strategies” (page 89). Focus on achieving great CPU-GPU parallelism first and
then assess whether concurrent programming can provide an additional performance benefit.
A good candidate has either or both of the following characteristics:
● The application performs many tasks on the CPU that are independent of OpenGL rendering. Games, for
example, simulate the game world, calculate artificial intelligence from computer-controlled opponents,
and play sound. You can exploit parallelism in thisscenario because many of these tasks are not dependent
on your OpenGL drawing code.
● Profiling your application has shown that your OpenGL rendering code spends a lot of time in the CPU.
In this scenario, the GPU is idle because your application is incapable of feeding it commands fast enough.
If your CPU-bound code has already been optimized, you may be able to improve its performance further
by splitting the work into tasks that execute concurrently.
If your application is blocked waiting for the GPU, and has no work it can perform in parallel with its OpenGL
drawing commands, then it is not a good candidate for concurrency. If the CPU and GPU are both idle, then
your OpenGL needs are probably simple enough that no further tuning is useful.
For more information on how to determine where your application spends its time, see “Tuning Your OpenGL
Application” (page 155).
OpenGL Restricts Each Context to a Single Thread
Each thread in an OS X process has a single current OpenGL rendering context. Every time your application
calls an OpenGL function, OpenGL implicitly looks up the context associated with the current thread and
modifies the state or objects associated with that context.
OpenGL is not reentrant. If you modify the same context from multiple threads simultaneously, the results are
unpredictable. Your application might crash or it might render improperly. If for some reason you decide to
set more than one thread to target the same context, then you must synchronize threads by placing a mutex
around all OpenGL calls to the context, such as gl* and CGL*. OpenGL commands that block—such as fence
commands—do not synchronize threads.
Concurrency and OpenGL
Identifying Whether an OpenGL Application Can Benefit from Concurrency
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
149GCD and NSOperationQueue objects can both execute your tasks on a thread of their choosing. They may
create a thread specifically for that task, or they may reuse an existing thread. But in either case, you cannot
guarantee which thread executes the task. For an OpenGL application, that means:
● Each task must set the context before executing any OpenGL commands.
● Your application must ensure that two tasks that access the same context are not allowed to execute
concurrently.
Strategies for Implementing Concurrency in OpenGL Applications
A concurrent OpenGL application wants to focus on CPU parallelism so that OpenGL can provide more work
to the GPU. Here are a few recommended strategies for implementing concurrency in an OpenGL application:
● Decompose your application into OpenGL and non-OpenGL tasks that can execute concurrently. Your
OpenGL rendering code executes as a single task, so it still executes in a single thread. This strategy works
best when your application has other tasks that require significant CPU processing.
●
If performance profiling reveals that your application spends a lot of CPU time inside OpenGL, you can
move some of that processing to another thread by enabling the multithreading in the OpenGL engine.
The advantage of this method is its simplicity; enabling the multithreaded OpenGL engine takes just a
few lines of code. See “Multithreaded OpenGL” (page 150).
●
If your application spends a lot of CPU time preparing data to send to openGL, you can divide the work
between tasks that prepare rendering data and tasks that submit rendering commands to OpenGL. See
“Perform OpenGL Computations in a Worker Task” (page 151)
●
If your application has multiple scenes it can render simultaneously or work it can perform in multiple
contexts, it can create multiple tasks, with an OpenGL context per task. If the contexts can share the same
resources, you can use contextsharing when the contexts are created to share surfaces or OpenGL objects:
display lists, textures, vertex and fragment programs, vertex array objects, and so on. See “Use Multiple
OpenGL Contexts” (page 153)
Multithreaded OpenGL
Whenever your application calls OpenGL, the renderer processes the parameters to put them in a format that
the hardware understands. The time required to process these commands varies depending on whether the
inputs are already in a hardware-friendly format, but there is always some overhead in preparing commands
for the hardware.
Concurrency and OpenGL
Strategies for Implementing Concurrency in OpenGL Applications
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
150If your application spends a lot of time performing calculations inside OpenGL, and you’ve already taken steps
to pick ideal data formats, your application might gain an additional benefit by enabling multithreading inside
the OpenGL engine. The multithreaded OpenGL engine automatically creates a worker thread and transfers
some of its calculationsto that thread. On a multicore system, this allowsinternal OpenGL calculations performed
on the CPU to act in parallel with your application, improving performance. Synchronizing functions continue
to block the calling thread.
Listing 14-1 shows the code required to enable the multithreaded OpenGL engine.
Listing 14-1 Enabling the multithreaded OpenGL engine
CGLError err = 0;
CGLContextObj ctx = CGLGetCurrentContext();
// Enable the multithreading
err = CGLEnable( ctx, kCGLCEMPEngine);
if (err != kCGLNoError )
{
// Multithreaded execution may not be available
// Insert your code to take appropriate action
}
Note: Enabling or disabling multithreaded execution causes OpenGL to flush previous commands
as well as incurring the overhead of setting up the additional thread. You should enable or disable
multithreaded execution in an initialization function rather than in the rendering loop.
Enabling multithreading comes at a cost—OpenGL must copy parameters to transmit them to the worker
thread. Because of this overhead, you should always test your application with and without multithreading
enabled to determine whether it provides a substantial performance improvement.
Perform OpenGL Computations in a Worker Task
Some applications perform lots of calculations on their data before passing that data down to the OpenGL
renderer. For example, the application might create new geometry or animate existing geometry. Where
possible,such calculationsshould be performed inside OpenGL. For example, vertex shaders and the transform
Concurrency and OpenGL
Perform OpenGL Computations in a Worker Task
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
151feedback extension might allow you to perform these calculations entirely within OpenGL. Thistakes advantage
of the greater parallelism available inside the GPU, and reduces the overhead of copying results between your
application and OpenGL.
The approach described in Figure 9-3 (page 92) alternates between updating OpenGL objects and executing
rendering commands that use those objects. OpenGL renders on the GPU in parallel with your application’s
updates running on the CPU. If the calculations performed on the CPU take more processing time than those
on the GPU, then the GPU spends more time idle. In this situation, you may be able to take advantage of
parallelism on systems with multiple CPUs. Split your OpenGL rendering code into separate calculation and
processing tasks, and run them in parallel. Figure 14-1 shows a clear division of labor. One task produces data
that is consumed by the second and submitted to OpenGL.
Figure 14-1 CPU processing and OpenGL on separate threads
CPU
Processing
Shared
data
Framebuffer
OpenGL
context
Texture data
Vertex data
OpenGL
state
OpenGL
surface
Thread 1 Thread 2
For best performance, your application should avoid copying data between the tasks. For example, rather than
calculating the data in one task and copying it into a vertex buffer object in the other, map the vertex buffer
object in the setup code and hand the pointer directly to the worker task.
If your application can further decompose the modifications task into subtasks, you may see better benefits.
For example, assume two or more vertex buffers, each of which needsto be updated before submitting drawing
commands. Each can be recalculated independently of the others. In this scenario, the modifications to each
buffer becomes an operation, using an NSOperationQueue object to manage the work:
1. Set the current context.
2. Map the first buffer.
3. Create an NSOperation object whose task is to fill that buffer.
4. Queue that operation on the operation queue.
Concurrency and OpenGL
Perform OpenGL Computations in a Worker Task
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
1525. Perform steps 2 through 4 for the other buffers.
6. Call waitUntilAllOperationsAreFinished on the operation queue.
7. Unmap the buffers.
8. Execute rendering commands.
On a multicore system, multiple threads of execution may allow the buffers to be filled simultaneously. Steps
7 and 8 could even be performed by a separate operation queued onto the same operation queue, provided
that operation set the proper dependencies.
Use Multiple OpenGL Contexts
If your application has multiple scenes that can be rendered in parallel, you can use a context for each scene
you need to render. Create one context for each scene and assign each context to an operation or task. Because
each task has its own context, all can submit rendering commands in parallel.
The Apple-specific OpenGL APIs also provide the option for sharing data between contexts, as shown in Figure
14-2. Shared resources are automatically set up as mutual exclusion (mutex) objects. Notice that thread 2
draws to a pixel buffer that is linked to the shared state as a texture. Thread 1 can then draw using that texture.
Figure 14-2 Two contexts on separate threads
Pbuffer
surface
Framebuffer
OpenGL context 1
OpenGL state 1
OpenGL context 2
OpenGL state 2
OpenGL
surface
Thread 1 Thread 2
OpenGL
shared state
OpenGL
shared state
OpenGL
shared state
This is the most complex model for designing an application. Changes to objects in one context must be
flushed so that other contextssee the changes. Similarly, when your application finishes operating on an object,
it must flush those commands before exiting, to ensure that all rendering commands have been submitted to
the hardware.
Concurrency and OpenGL
Use Multiple OpenGL Contexts
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
153Guidelines for Threading OpenGL Applications
Follow these guidelines to ensure successful threading in an application that uses OpenGL:
● Use only one thread per context. OpenGL commands for a specific context are not thread safe. You should
never have more than one thread accessing a single context simultaneously.
● Contexts that are on different threads can share object resources. For example, it is acceptable for one
context in one thread to modify a texture, and a second context in a second thread to modify the same
texture. The shared object handling provided by the Apple APIs automatically protects against thread
errors. And, your application is following the "one thread per context" guideline.
● When you use an NSOpenGLView object with OpenGL calls that are issued from a thread other than the
main one, you must set up mutex locking. Mutex locking is necessary because unless you override the
default behavior, the main thread may need to communicate with the view for such things as resizing.
Applications that use Objective-C with multithreading can lock contexts using the functions
CGLLockContext and CGLUnlockContext. If you want to perform rendering in a thread other than the
main one, you can lock the context that you want to access and safely execute OpenGL commands. The
locking calls must be placed around all of your OpenGL calls in all threads.
CGLLockContext blocks the thread it is on until all other threads have unlocked the same context using
the function CGLUnlockContext. You can use CGLLockContext recursively. Context-specific CGL calls
by themselves do not require locking, but you can guarantee serial processing for a group of calls by
surrounding them with CGLLockContext and CGLUnlockContext. Keep in mind that calls from the
OpenGL API (the API provided by the Khronos OpenGL Working Group) require locking.
● Keep track of the current context. When switching threadsit is easy to switch contextsinadvertently, which
causes unforeseen effects on the execution of graphic commands. You must set a current context when
switching to a newly created thread.
Concurrency and OpenGL
Guidelines for Threading OpenGL Applications
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
154After you design and implement your application, it is important that you spend some time analyzing its
performance. The key to performance tuning your OpenGL application is to successively refine the design and
implementation of your application. You do this by alternating between measuring your application, identifying
where the bottleneck is, and removing the bottleneck.
If you are unfamiliar with general performance issues on the Macintosh platform, you will want to read Getting
Started with Performance and Performance Overview. Performance Overview contains general performance
tips that are useful to all applications. It also describes most of the performance tools provided with OS X.
Next, take a close look at Instruments. Instruments consolidates many measurement tools into a single
comprehensive performance-tuning application.
There are two tools other than OpenGL Profiler that are specific for OpenGL development—OpenGL Driver
Monitor and OpenGL Shader Builder. OpenGL Driver Monitor collectsreal-time data from the hardware. OpenGL
Shader Builder provides immediate feedback on vertex and fragment programs that you write.
For more information on these tools, see:
● OpenGL Tools for Serious Graphics Development
● Optimizing with Shark: Big Payoff, Small Effort
●
Instruments User Guide
● Shark User Guide
● Real world profiling with the OpenGL Profiler
● OpenGL Driver Monitor User Guide
● OpenGL Shader Builder User Guide
The following books contain many techniques for getting the most performance from the GPU:
● GPU Gems: Programming Techniques, Tips and Tricks for Real Time Graphics, Randima Fernando. In
particular, Graphics Pipeline Performance is a critical article for understanding how to find the bottlenecks
in your OpenGL application.
● GPU Gems 2: Programming Techniques for High-Performance Graphics and General-Purpose Computation ,
Matt Pharr and Randima Fernando.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
155
Tuning Your OpenGL ApplicationThis chapter focuses on two main topics:
●
“Gathering and Analyzing Baseline Performance Data” (page 156) shows how to use top and OpenGL
Profiler to obtain and interpret baseline performance data.
●
“Identifying Bottlenecks with Shark” (page 161) discussesthe patterns of usage that the Shark performance
tool can make apparent and that indicate places in your code that you may want to improve.
Gathering and Analyzing Baseline Performance Data
Analyzing performance is a systematic process that starts with gathering baseline data. OS X provides several
applications that you can use to assess baseline performance for an OpenGL application:
● top is a command-line utility that you run in the Terminal window. You can use top to assess how much
CPU time your application consumes.
● OpenGL Profiler is an application that determines how much time an application spends in OpenGL. It
also provides function traces that you can use to look for redundant calls.
● OpenGL Driver Monitor lets you gather real-time data on the operation of the GPU and lets you look at
information (OpenGL extensions supported, buffer modes, sample modes, and so forth) for the available
renderers. For more information, see OpenGL Tools for Serious Graphics Development.
This section shows how to use top along with OpenGL Profiler to analyze where to spend your optimization
efforts—in your OpenGL code, your other application code, or in both. You'll see how to gather baseline data
and how to determine the relationship of OpenGL performance to overall application performance.
1. Launch your OpenGL application.
2. Open a Terminal window and place it side-by-side with your application window.
3. In the Terminal window, type top and press Return. You'll see output similar to that shown in Figure 15-1.
Tuning Your OpenGL Application
Gathering and Analyzing Baseline Performance Data
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
156The top program indicates the amount of CPU time that an application uses. The CPU time serves as a
good baseline value for gauging how much tuning your code needs. Figure 15-1 shows the percentage
of CPU time for the OpenGL application GLCarbon1C (highlighted). Note this application utilizes 31.5% of
CPU resources.
Figure 15-1 Output produced by the top application
Tuning Your OpenGL Application
Gathering and Analyzing Baseline Performance Data
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
1574. Open the OpenGL Profiler application, located in /Developer/Applications/Graphics Tools/. In
the window that appears, select the options to collect a trace and include backtraces, as shown in Figure
15-2.
Figure 15-2 The OpenGL Profiler window
5. Select the option “Attach to application”, then select your application from the Application list.
You may see small pauses or stutters in the application, particularly when OpenGL Profiler is collecting a
function trace. This is normal and does not significantly affect the performance statistics. The glitches are
due to the large amount of data that OpenGL Profiler is writing out.
6. Click Suspend to stop data collection.
7. Open the Statistics and Trace windows by choosing them from the Views menu.
Figure 15-3 provides an example of what the Statistics window looks like. Figure 15-4 (page 160) shows a
Trace window.
The estimated percentage of time spent in OpenGL is shown at the bottom of Figure 15-3. Note that for
this example, it is 28.91%. The higher this number, the more time the application is spending in OpenGL
and the more opportunity there may be to improve application performance by optimizing OpenGL code.
Tuning Your OpenGL Application
Gathering and Analyzing Baseline Performance Data
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
158You can use the amount of time spent in OpenGL along with the CPU time to calculate a ratio of the
application time versus OpenGL time. Thisratio indicates where to spend most of your optimization efforts.
Figure 15-3 A statistics window
8. In the Trace window, look for duplicate function calls and redundant or unnecessary state changes.
Look for back-to-back function calls with the same or similar data. These are areas that can typically be
optimized. Functions that are called more than necessary include glTexParameter, glPixelStore,
glEnable, and glDisable. For most applications, these functions can be called once from a setup or
state modification routine and called only when necessary.
It's generally good practice to keep state changes out of rendering loops(which can be seen in the function
trace as the same sequence of state changes and drawing over and over again) as much as possible and
use separate routines to adjust state as necessary.
Tuning Your OpenGL Application
Gathering and Analyzing Baseline Performance Data
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
159Look at the time value to the left of each function call to determine the cost of the call.
Figure 15-4 A Trace window
Use these to determine
the cost of a call
9. Determine what the performance gain would be if it were possible to reduce the time to execute all
OpenGL calls to zero.
For example, take the performance data from the GLCarbon1C application used in thissection to determine
the performance attributable to the OpenGL calls.
Total Application Time (from top) = 31.5%
Total Time in OpenGL (from OpenGL Profiler) = 28.91%
At first glance, you might think that optimizing the OpenGL code could improve application performance
by almost 29%, thusreducing the total application time by 29%. Thisisn't the case. Calculate the theoretical
performance increase by multiplying the total CPU time by the percentage of time spent in OpenGL. The
theoretical performance improvement for this example is:
31.5 X .2891 = 9.11%
If OpenGL took no time at all to execute, the application would see a 9.11% increase in performance. So,
if the application runs at 60 frames per second (FPS), it would perform as follows:
New FPS = previous FPS * (1 +(% performance increase)) = 60 fps *(1.0911) =
65.47 fps
The application gains almost 5.5 frames per second by reducing OpenGL from 28.91% to 0%. This shows
that the relationship of OpenGL performance to application performance is not linear. Simply reducing
the amount of time spent in OpenGL may or may not offer any noticeable benefit in application
performance.
Tuning Your OpenGL Application
Gathering and Analyzing Baseline Performance Data
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
160Using OpenGL Driver Monitor to Measure Stalls
You can use OpenGL Driver Monitor to measure how long the CPU waits for the GPU, as shown in Figure 15-5.
OpenGL Driver Monitor is useful for analyzing other parameters as well. You can choose which parameters to
monitor simply by clicking a parameter name from the drawer shown in the figure.
Figure 15-5 The graph view in OpenGL Driver Monitor
Identifying Bottlenecks with Shark
Shark is an extremely useful tool for identifying places in your code that are slow and could benefit from
optimization. Once you learn the basics, you can use it on your OpenGL applications to identify bottlenecks.
There are three issues to watch out for in Shark when using it to analyze OpenGL performance:
● Costly data conversions. If you notice the glgProcessPixels call (in the libGLImage.dylib library)
showing up in the analysis, it's an indication that the driver is not handling a texture upload optimally.
The call is used when your application makes a glTexImage or glTexSubImage call using data that is
in a nonnative format for the driver, which meansthe data must be converted before the driver can upload
it. You can improve performance by changing your data so that it is in a native format for the driver. See
“Use Optimal Data Types and Formats” (page 102).
Tuning Your OpenGL Application
Identifying Bottlenecks with Shark
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
161Note: If your data needs only to be swizzled, glgProcessPixels performs the swizzling
reasonably fast although not as fast as if the data didn't need swizzling. But non-native data
formats are converted one byte at a time and incurs a performance cost that is best to avoid.
● Time in the mach_kernel library. If you see time spent waiting for a timestamp or waiting for the driver,
it indicates that your application is waiting for the GPU to finish processing. You see this during a texture
upload, for example.
● Misleading symbols. You may see a symbol, such as glgGetString, that appears to be taking time but
shouldn't be taking time in your application. Thatsometimes happens because the underlying optimizations
performed by the system don't have any symbols attached to them on the driver side. Without a symbol
to display, Shark shows the last symbol. You need to look for the call that your application made prior to
that symbol and focus your attention there. You don't need to concern yourself with the calls that were
made "underneath" your call.
Tuning Your OpenGL Application
Identifying Bottlenecks with Shark
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
162OpenGL functionality changes with each version of the OpenGL API. This appendix describes the functionality
that was added with each version. See the official OpenGL specification for detailed information.
The functionality for each version is guaranteed to be available through the OpenGL API even if a particular
renderer does not support all of the extensions in a version. For example, a renderer that claims to support
OpenGL 1.3 might not export the GL_ARB_texture_env_combine or GL_EXT_texture_env_combine
extensions. It's important that you query both the renderer version and extension string to make sure that the
renderer supports any functionality that you want to use.
Note: It's possible for vendor and ARB extensions to provide similar functionality. As particular
functionality becomes widely adopted, it can be moved into the core OpenGL API. As a result,
functionality that you want to use could be included as an extension, as part of the core API, or both.
You should read the extensions and the core OpenGL specifications carefully to see the differences.
Furthermore, as an extension is promoted, the API associated with that functionality can change.
For more information,see “Determining the OpenGL Capabilities Supported by the Renderer” (page
83).
In the following tables, the extensions describe the feature that the core functionality is based on. The core
functionality might not be the same as the extension. For example, compare the core texture crossbar
functionality with the extension that it's based on.
Version 1.1
Table A-1 Functionality added in OpenGL 1.1
Functionality Extension
Copy texture and subtexture GL_EXT_copy_texture and GL_EXT_subtexture
Logical operation GL_EXT_blend_logic_op
Polygon offset GL_EXT_polygon_offset
Texture image formats GL_EXT_texture
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
163
Legacy OpenGL Functionality by VersionFunctionality Extension
Texture objects GL_EXT_texture_object
Texture proxies GL_EXT_texture
Texture replace environment GL_EXT_texture
Vertex array GL_EXT_vertex_array
There were a number of other minor changes outlined in Appendix C section 9 of the OpenGL specification.
See http://www.opengl.org.
Version 1.2
Table A-2 Functionality added in OpenGL 1.2
Functionality Extension
BGRA pixel formats GL_EXT_bgra
GL_SGI_color_table , GL_EXT_color_subtable,
GL_EXT_convolution,GL_HP_convolution_border_modes,
GL_SGI_color_matrix, GL_EXT_histogram,
GL_EXT_blend_minmax, and GL_EXT_blend_subtract
Imaging subset (optional)
Normal rescaling GL_EXT_rescale_normal
Packed pixel formats GL_EXT_packed_pixels
Separate specular color GL_EXT_separate_specular_color
Texture coordinate edge clamping GL_SGIS_texture_edge_clamp
Texture level of detail control GL_SGIS_texture_lod
Three-dimensional texturing GL_EXT_texture3D
Vertex array draw element range GL_EXT_draw_range_elements
Legacy OpenGL Functionality by Version
Version 1.2
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
164Note: The imaging subset might not be present on all implementations; you must verify by checking
for the ARB_imaging extension.
OpenGL 1.2.1 introduced ARB extensions with no specific core API changes.
Version 1.3
Table A-3 Functionality added in OpenGL 1.3
Functionality Extension
Compressed textures GL_ARB_texture_compression
Cube map textures GL_ARB_texture_cube_map
Multisample GL_ARB_multisample
Multitexture GL_ARB_multitexture
Texture add environment mode GL_ARB_texture_env_add
Texture border clamp GL_ARB_texture_border_clamp
Texture combine environment mode GL_ARB_texture_env_combine
Texture dot3 environment mode GL_ARB_texture_env_dot3
Transpose matrix GL_ARB_transpose_matrix
Version 1.4
Table A-4 Functionality added in OpenGL 1.4
Functionality Extension
Automatic mipmap generation GL_SGIS_generate_mipmap
Blend function separate GL_ARB_blend_func_separate
Blend squaring GL_NV_blend_square
Depth textures GL_ARB_depth_texture
Legacy OpenGL Functionality by Version
Version 1.3
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
165Functionality Extension
Fog coordinate GL_EXT_fog_coord
Multiple draw arrays GL_EXT_multi_draw_arrays
Point parameters GL_ARB_point_parameters
Secondary color GL_EXT_secondary_color
Separate blend functions GL_EXT_blend_func_separate, GL_EXT_blend_color
Shadows GL_ARB_shadow
Stencil wrap GL_EXT_stencil_wrap
Texture crossbar environment mode GL_ARB_texture_env_crossbar
Texture level of detail bias GL_EXT_texture_lod_bias
Texture mirrored repeat GL_ARB_texture_mirrored_repeat
Window raster position GL_ARB_window_pos
Version 1.5
Table A-5 Functionality added in OpenGL 1.5
Functionality Extension
Buffer objects GL_ARB_vertex_buffer_object
Occlusion queries GL_ARB_occlusion_query
Shadow functions GL_EXT_shadow_funcs
Version 2.0
Table A-6 Functionality added in OpenGL 2.0
Functionality Extension
Multiple render targets GL_ARB_draw_buffers
Legacy OpenGL Functionality by Version
Version 1.5
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
166Functionality Extension
Non–power-of-two textures GL_ARB_texture_non_power_of_two
Point sprites GL_ARB_point_sprite
Separate blend equation GL_EXT_blend_equation_separate
GL_ATI_separate_stencil
GL_EXT_stencil_two_side
Separate stencil
Shading language GL_ARB_shading_language_100
Shader objects GL_ARB_shader_objects
GL_ARB_fragment_shader
GL_ARB_vertex_shader
Shader programs
Version 2.1
Table A-7 Functionality added in OpenGL 2.1
Functionality Extension
Pixel buffer objects GL_ARB_pixel_buffer_object
sRGB textures GL_EXT_texture_sRGB
Legacy OpenGL Functionality by Version
Version 2.1
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
167The OpenGL 3.0 specification deprecated many areas of functionality defined in earlier versions of the OpenGL
specification. The OpenGL 3.2 Core profile explicitly removesthese deprecated features and adjusts other parts
of the specification to provide a streamlined, clean programming interface to OpenGL. Use this chapter to
assist you in migrating your application away from this deprecated functionality.
Removed Functionality
The features that were removed from OpenGL are described in in Appendix E of the OpenGL 3.2 Core
specification, and you should use that as the definitive guide for the changes you need to make in your
application. Here is a summary of most significant areas that changed:
●
If your application uses the fixed-function pipeline, it must be rewritten to use shaders instead.
●
If your application uses shaders, you must rewrite your shaders to use OpenGL Shading Language 1.5;
many built-in shader variables provided in earlier versions of the OpenGL Shading Language were explicitly
removed from the OpenGL Shading Language 1.5 specification. Similarly, your application may no longer
provide vertex data using the fixed-function routines; all vertex attributes are now specified as generic
vertex attributes.
● Your application must explicitly generate object names using the OpenGL API.
● Vertex data must be provided to OpenGL using buffer objects.
● The built-in matrix stack functionality from earlier versions of OpenGL has been removed; you must recreate
this functionality using shader inputs.
● Support for auxiliary and accumulation buffers has been removed; use framebuffer objects instead.
● Your application no longer fetches the list of extensions as a single string. Instead, you first fetch the
number of extensions and then separately fetch each extension string.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
168
Updating an Application to Support the OpenGL
3.2 Core SpecificationExtension Changes on OS X
OpenGL 3.2 providesfunctionality that earlier versions ofOpenGL provided through extensions.Other extensions
that were previously supported on OS X are no longer supported when your application uses the OpenGL 3.2
Core profile. Table B-1 lists extensions described elsewhere in this guide; use this table to determine whether
the extension is supported, and if not, what equivalent functionality is supported.
Table B-1 Extensions described in this guide
Extension Status
Obsolete. Use the ARB_Sync functionality provided by OpenGL 3.2
(Core).
APPLE_fence
ARB_vertex_buffer_object Functionality provided by OpenGL 3.2 (Core).
Obsolete. Use the ARB_vertex_array_object functionality provided
by OpenGL 3.2 (Core).
APPLE_vertex_array_object
Obsolete. Use the ARB_map_buffer_range functionality provided by
OpenGL 3.2 (Core).
APPLE_vertex_array_range
Obsolete. Use the ARB_map_buffer_range functionality provided by
OpenGL 3.2 (Core).
APPLE_flush_buffer_range
APPLE_client_storage Supported.
APPLE_texture_range Supported.
ARB_texture_rectangle Functionality provided by OpenGL 3.2 (Core).
ARB_shader_objects Functionality provided by OpenGL 3.2 (Core).
ARB_vertex_shader Functionality provided by OpenGL 3.2 (Core).
ARB_fragment_shader Functionality provided by OpenGL 3.2 (Core).
EXT_transform_feedback Functionality provided by OpenGL 3.2 (Core).
EXT_gpu_shader4 Obsolete. Functionality included in GLSL 1.5
EXT_geometry_shader4 Functionality provided by OpenGL 3.2 (Core).
Obsolete. Use the ARB_uniform_buffer_object functionality
provided by OpenGL 3.2 (Core).
EXT_bindable_uniform
ARB_pixel_buffer_object Functionality provided by OpenGL 3.2 (Core).
Updating an Application to Support the OpenGL 3.2 Core Specification
Extension Changes on OS X
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
169Extension Status
Obsolete. Use the ARB_framebuffer_object functionality provided
by OpenGL 3.2 (Core).
EXT_framebuffer_object
APPLE_pixel_buffer Obsolete. Use framebuffer objects instead.
Obsolete. Use multisampled renderbuffers to precisely control
multisampling.
NV_multisample_filter_hint
Updating an Application to Support the OpenGL 3.2 Core Specification
Extension Changes on OS X
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
170Function pointers to OpenGL routines allow you to deploy your application across multiple versions of OS X
regardless of whether the entry point is supported at link time or runtime. This practice also provides support
for code that needs to run cross-platform—in both OS X and Windows.
Note: If you are deploying your application only in OS X v10.4 or later, you do not need to read this
chapter. Instead, consider the alternative, which is to set the gcc attribute that allows weak linking
of symbols. Keep in mind, however, that weak linking may impact your application's performance.
For more information, see “Frameworks and Weak Linking”.
This appendix discusses the tasks needed to set up and use function pointers as entry points to OpenGL
routines:
●
“Obtaining a Function Pointer to an Arbitrary OpenGL Entry Point” (page 171)shows how to write a generic
routine that you can reuse for any OpenGL application on the Macintosh platform.
●
“Initializing Entry Points” (page 172) describes how to declare function pointer type definitions and initialize
them with the appropriate OpenGL command entry points for your application.
Obtaining a Function Pointer to an Arbitrary OpenGL Entry Point
Getting a pointer to an OpenGL entry point function is fairly straightforward from Cocoa. You can use the
Dynamic Loader function NSLookupAndBindSymbol to get the address of an OpenGL entry point.
Keep in mind that getting a valid function pointer means that the entry point is exported by the OpenGL
framework; it does not guarantee that a particular routine is supported and valid to call from within your
application. You still need to check for OpenGL functionality on a per-renderer basis as described in “Detecting
Functionality” (page 83).
Listing C-1 shows how to use NSLookupAndBindSymbol from within the function MyNSGLGetProcAddress.
When provided a symbol name, this application-defined function returns the appropriate function pointer
from the global symbol table. A detailed explanation for each numbered line of code appears following the
listing.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
171
Setting Up Function Pointers to OpenGL RoutinesListing C-1 Using NSLookupAndBindSymbol to obtain a symbol for a symbol name
#import
#import
#import
void * MyNSGLGetProcAddress (const char *name)
{
NSSymbol symbol;
char *symbolName;
symbolName = malloc (strlen (name) + 2); // 1
strcpy(symbolName + 1, name); // 2
symbolName[0] = '_'; // 3
symbol = NULL;
if (NSIsSymbolNameDefined (symbolName)) // 4
symbol = NSLookupAndBindSymbol (symbolName);
free (symbolName); // 5
return symbol ? NSAddressOfSymbol (symbol) : NULL; // 6
}
Here's what the code does:
1. Allocates storage for the symbol name plus an underscore character ('_'). The underscore character is
part of the UNIX C symbol-mangling convention, so make sure that you provide storage for it.
2. Copiesthe symbol name into the string variable,starting at the second character, to leave room for prefixing
the underscore character.
3. Copies the underscore character into the first character of the symbol name string.
4. Checks to make sure that the symbol name is defined, and if it is, looks up the symbol.
5. Frees the symbol name string because it is no longer needed.
6. Returns the appropriate pointer if successful, or NULL if not successful. Before using this pointer, you
should make sure that is it valid.
Initializing Entry Points
Listing C-2 shows how to use the MyNSGLGetProcAddress function from Listing C-1 (page 172) to obtain a
few OpenGL entry points. A detailed explanation for each numbered line of code appears following the listing.
Setting Up Function Pointers to OpenGL Routines
Initializing Entry Points
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
172Listing C-2 Using NSGLGetProcAddress to obtain an OpenGL entry point
#import "MyNSGLGetProcAddress.h" // 1
static void InitEntryPoints (void);
static void DeallocEntryPoints (void);
// Function pointer type definitions
typedef void (*glBlendColorProcPtr)(GLclampf red,GLclampf green,
GLclampf blue,GLclampf alpha);
typedef void (*glBlendEquationProcPtr)(GLenum mode);
typedef void (*glDrawRangeElementsProcPtr)(GLenum mode, GLuint start,
GLuint end,GLsizei count,GLenum type,const GLvoid *indices);
glBlendColorProcPtr pfglBlendColor = NULL; // 2
glBlendEquationProcPtr pfglBlendEquation = NULL;
glDrawRangeElementsProcPtr pfglDrawRangeElements = NULL;
static void InitEntryPoints (void) // 3
{
pfglBlendColor = (glBlendColorProcPtr) MyNSGLGetProcAddress
("glBlendColor");
pfglBlendEquation = (glBlendEquationProcPtr)MyNSGLGetProcAddress
("glBlendEquation");
pfglDrawRangeElements = (glDrawRangeElementsProcPtr)MyNSGLGetProcAddress
("glDrawRangeElements");
}
// -------------------------
static void DeallocEntryPoints (void) // 4
{
pfglBlendColor = NULL;
pfglBlendEquation = NULL;
pfglDrawRangeElements = NULL;;
}
Here's what the code does:
1. Imports the header file that contains the MyNSGLProcAddress function from Listing C-1 (page 172).
Setting Up Function Pointers to OpenGL Routines
Initializing Entry Points
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
1732. Declares function pointers for the functions of interest. Note that each function pointer uses the prefix pf
to distinguish it from the function it points to. Although using this prefix is not a requirement, it's best to
avoid using the exact function names.
3. Initializes the entry points. This function repeatedly calls the MyNSGLProcAddress function to obtain
function pointers for each of the functions of interest—glBlendColor, glBlendEquation, and
glDrawRangeElements.
4. Sets each of the function pointers to NULL when they are no longer needed.
Setting Up Function Pointers to OpenGL Routines
Initializing Entry Points
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
174This table describes the changes to OpenGL Programming Guide for Mac .
Date Notes
2012-07-23 Updated with information on supporting high-resolution displays.
2011-06-06 Added new context options.
2010-11-15 Fixed a few small errors in the texture chapter.
Updated the recommendations on when to use each texture uploading
and downloading technique.
Updated the code for creating a texture from a view’s contents to use
newer, better supported techniques.
2010-06-14 Corrected texture creation code snippets.
2010-03-24 Minor updates and clarifications.
Substantial revisions to describe behaviors for OpenGL on OS X v10.5 and
OS X v10.6. Removed information on obsolete and deprecated behaviors.
2010-02-24
Corrected errors in code listings. Pixel format attribute lists should be
terminated with 0, not NULL. One call to glTexImage2D had an incorrect
number of parameters.
2009-08-28
Updated the Cocoa OpenGL tutorial and made numerous other minor
changes.
2008-06-09
Fixed compilation errors in Listing 8-1 (page 84).
Added “Getting Decompressed Raw Pixel Data from a Source Image” (page
135).
Updated links to OpenGL extensions.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
175
Document Revision HistoryDate Notes
Made several minor edits.
2007-12-04 Corrected minor typographical and technical errors.
Added “Ensuring That Back Buffer Contents Remain the Same” (page 66).
Revised “Deprecated Attributes” (page 70).
2007-08-07 Fixed several technical issues.
2007-05-29 Fixed a broken link.
2007-05-17 Fixed a few technical inaccuracies in the code listings.
Changed attribs to attributes in Listing 6-2 (page 68).
Fixed drawRect method implementation in “Drawing to a Window or
View” (page 35).
2006-12-20 Fixed minor errors.
Added information concerning the Apple client storage extension. Fixed
a typographical error.
2006-11-07 Added information about performance issues and processor queries.
See “Determining Whether Vertex and Fragment Processing Happens on
the GPU” (page 78).
2006-10-03 Added a section on checking for GPU processing.
Added “Determining Whether Vertex and Fragment Processing Happens
on the GPU” (page 78).
Fixed a number of minor typos in the code and in the text.
2006-09-05 Fixed minor technical problems.
2006-07-24 Made minor technical and typograhical changes throughout.
Added information to “Surface Drawing Order Specifies the Position of
the OpenGL Surface Relative to the Window” (page 77).
Document Revision History
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
176Date Notes
Changed glCopyTexSubImage to glCopyTexSubImage2D in
“Downloading Texture Data” (page 136).
Made minor improvements to Listing 11-6 (page 136).
Removed information about 1-D textures.
2006-06-28 Made several minor technical corrections.
Redirected links to the OpenGL specification for the framebuffer object
extension so that they point to the SGI Open Source website, which hosts
the most up-to-date version of this specification.
Removed the logic operation blending entry from Table A-6 (page 166)
because this functionality is not available in OpenGL 2.0.
2006-05-23 First version.
This document replaces Macintosh OpenGL Programming Guide and AGL
Programming Guide .
This document incorporates information from the following Technical
Notes:
TN2007 “The CGDirectDisplay API”
TN2014 “Insights on OpenGL”
TN2080 “Understanding and Detecting OpenGL Functionality”
TN2093 “OpenGL Performance Optimization: The Basics”
This document incorporates information from the following Technical
Q&As:
Technical Q&A OGL01 “aglChoosePixelFormat, The Inside Scoop”
Technical Q&A OGL02 “Correct Setup of an AGLDrawable”
Technical Q&A QA1158 “glFlush() vs. glFinish()”
Technical Q&A QA1167 “Using Interface Builder's NSOpenGLView or
Custom View objects for an OpenGL application”
Technical Q&A QA1188 “GetProcAdress and OpenGL Entry Points”
Document Revision History
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
177Date Notes
Technical Q&A QA1209 “Updating OpenGL Contexts”
Technical Q&A QA1248 “Context Sharing Tips”
Technical Q&A QA1268 “Sharpening Full Scene Anti-Aliasing Details”
Technical Q&A QA1269 “OS X OpenGL Interfaces”
Technical Q&A QA1325 “Creating an OpenGL texture from an NSView”
Document Revision History
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
178This glossary containstermsthat are used specifically
for the Apple implementation of OpenGL and a few
terms that are common in graphics programming.
For definitions of additional OpenGL terms, see
OpenGL Programming Guide, by the Khronos
OpenGL Working Group
aliased Said of graphics whose edges appear
jagged; can be remedied by performing antialiasing
operations.
antialiasing In graphics, a technique used to
smooth and soften the jagged (or aliased) edges
that are sometimes apparent when graphical objects
such as text, line art, and images are drawn.
ARB The Khronos OpenGL Working Group, which
is the group that oversees the OpenGL specification
and extensions to it.
attach To establish a connection between two
existing objects. Compare bind.
bind To create a new object and then establish a
connection between that object and a rendering
context. Compare attach.
bitmap A rectangular array of bits.
bitplane A rectangular array of pixels.
buffer A block of memory dedicated to storing a
specific kind of data, such as depth values, green
color values, stencil index values, and color index
values.
CGL (Core OpenGL) framework The Apple
framework for using OpenGL graphics in OS X
applications that need low-level access to OpenGL.
clipping An operation that identifies the area of
drawing. Anything not in the clipping region is not
drawn.
clip coordinates The coordinate system used for
view-volume clipping. Clip coordinates are applied
after applying the projection matrix and prior to
perspective division.
color lookup table A table of values used to map
color indexes into actual color values.
completeness A state that indicates whether a
framebuffer object meets all the requirements for
drawing.
context A set of OpenGL state variables that affect
how drawing is performed for a drawable object
attached to that context. Also called a rendering
context.
culling Eliminating parts of a scene that can't be
seen by the observer.
current context The rendering context to which
OpenGL routes commands issued by your
application.
current matrix A matrix used by OpenGL to
transform coordinates in one system to those of
another system, such as the modelview matrix, the
perspective matrix, and the texture matrix. GL
shading language allows user-defined matrices.
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
179
Glossarydepth In OpenGL, refers to the z coordinate and
specifies how far a pixel lies from the observer.
depth buffer A block of memory used to store a
depth value for each pixel. The depth buffer is used
to determine whether or not a pixel can be seen by
the observer. Those that are hidden are typically
removed.
display list A list of OpenGL commands that have
an associated name and that are uploaded to the
GPU, preprocessed, and then executed at a later
time. Display lists are often used for
computing-intensive commands.
double buffering The practice of using a front and
back color buffer to achieve smooth animation. The
back buffer is not displayed, but swapped with the
front buffer.
drawable object In OS X, an object allocated
outside of OpenGL that can serve as an OpenGL
framebuffer. A drawable object can be any of the
following: a window, a view, a pixel buffer, offscreen
memory, or a full-screen graphics device. See also
framebuffer object
extension A feature of OpenGL that's not part of
the OpenGL core API and therefore not guaranteed
to be supported by every implementation of
OpenGL. The naming conventions used for
extensions indicate how widely accepted the
extension is. The name of an extension supported
only by a specific company includes an abbreviation
of the company name. If more then one company
adoptsthe extension,the extension name is changed
to include EXT instead of a company abbreviation.
If the Khronos OpenGL Working Group approves an
extension, the extension name changes to include
ARB instead of EXT or a company abbreviation.
eye coordinates The coordinate system with the
observer at the origin. Eye coordinates are produced
by the modelview matrix and passed to the
projection matrix.
fence A token used by the GL_APPLE_fence
extension to determine whether a given command
has completed or not.
filtering A process that modifies an image by
combining pixels or texels.
fog An effect achieved by fading colors to a
background color based on the distance from the
observer. Fog provides depth cues to the observer.
fragment The color and depth values for a single
pixel; can also include texture coordinate values. A
fragment is the result of rasterizing primitives.
framebuffer The collection of buffers associated
with a window or a rendering context.
framebuffer attachable image The rendering
destination for a framebuffer object.
framebuffer object An OpenGL extension that
allows rendering to a destination other than the
usual OpenGL buffers or destinations provided by
the windowing system. A framebuffer object (FBO)
contains state information for the OpenGL
framebuffer and its set of images. A framebuffer
object is similar to a drawable object, except that a
drawable object is a window-system specific object
whereas a framebuffer object is a window-agnostic
object. The context that's bound to a framebuffer
object can be bound to a window-system-provided
drawable object for the purpose of displaying the
content associated with the framebuffer object.
frustum The region of space that is seen by the
observer and that is warped by perspective division.
Glossary
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
180FSAA (full scene antialiasing) A technique that
takes multiple samples at a pixel and combinesthem
with coverage values to arrive at a final fragment.
gamma correction A function that changes color
intensity valuesto correct for the nonlinear response
of the eye or of a display.
GLU Graphics library utilities.
GL Graphics library.
GLUT Graphics Library Utilities Toolkit, which is
independent of the window system. In OS X, GLUT
is implemented on top of Cocoa.
GLX An OpenGL extension that supports using
OpenGL within a window provided by the X Window
system.
image A rectangular array of pixels.
immediatemode The practice ofOpenGL executing
commands at the time an application issues them.
To prevent commands from being issued
immediately, an application can use a display list.
interleaved data Arrays of dissimilar data that are
grouped together, such as vertex data and texture
coordinates. Interleaving can speed data retrieval.
mipmaps A set of texture maps, provided at various
resolutions, whose purpose is to minimize artifacts
that can occur when a texture is applied to a
geometric primitive whose onscreen resolution
doesn't match the source texture map. Mipmapping
derivesfromthe latin phrasemultumin parvo , which
means "many things in a small place."
modelview matrix A 4 X 4 matrix used by OpenGL
to transforms points, lines, polygons, and positions
from object coordinates to eye coordinates.
mutex A mutual exclusion object in a multithreaded
application.
NURBS (nonuniform rational basis spline) A
methodology use to specify parametric curves and
surfaces.
packing Converting pixel color components from
a buffer into the format needed by an application.
pbuffer See pixel buffer.
pixel A picture element; the smallest element that
the graphics hardware can display on the screen. A
pixel is made up of all the bits at the location x , y ,
in all the bitplanes in the framebuffer.
pixel buffer A type of drawable object that allows
the use of offscreen buffers as sources for OpenGL
texturing. Pixel buffers allow hardware-accelerated
rendering to a texture.
pixel depth The number of bits per pixel in a pixel
image.
pixel format A format used to store pixel data in
memory. The format describesthe pixel components
(that is, red, blue, green, alpha), the number and
order of components, and other relevant
information,such as whether a pixel containsstencil
and depth values.
primitives The simplest elements in
OpenGL—points, lines, polygons, bitmaps, and
images.
projection matrix A matrix that OpenGL uses to
transform points, lines, polygons, and positionsfrom
eye coordinates to clip coordinates.
rasterization The process of converting vertex and
pixel data to fragments, each of which corresponds
to a pixel in the framebuffer.
renderbuffer A rendering destination for a 2D pixel
image, used for generalized offscreen rendering, as
defined in the OpenGL specification for the
GL_EXT_framebuffer_object extension.
Glossary
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
181renderer A combination of hardware and software
that OpenGL uses to create an image from a view
and a model. The hardware portion of a renderer is
associated with a particular display device and
supports specific capabilities, such as the ability to
support a certain color depth or buffering mode. A
renderer that uses only software is called a software
renderer and is typically used as a fallback.
rendering context A container forstate information.
rendering pipeline The order of operations used
by OpenGL to transform pixel and vertex data to an
image in the framebuffer.
render-to-texture An operation that draws content
directly to a texture target.
RGBA Red, green, blue, and alpha color
components.
shader A programthat computessurface properties.
shading language A high-level language, accessible
in C, used to produce advanced imaging effects.
stencil buffer Memory used specifically for stencil
testing. A stencil test is typically used to identify
masking regions, to identify solid geometry that
needs to be capped, and to overlap translucent
polygons.
surface The internal representation of a single
buffer that OpenGL actually drawsto and readsfrom.
For windowed drawable objects, thissurface is what
the OS X window server uses to composite OpenGL
content on the desktop.
tearing A visual anomaly caused when part of the
current frame overwrites previous frame data in the
framebuffer before the current frame is fully
rendered on the screen.
tessellation An operation that reduces a surface to
a mesh of polygons, or a curve to a sequence of lines.
texel A texture element used to specify the color
to apply to a fragment.
texture Image data used to modify the color of
rasterized fragments; can be one-, two-, or threedimensional or be a cube map.
texture mapping The process of applying a texture
to a primitive.
texture matrix A 4 x 4 matrix that OpenGL uses to
transform texture coordinates to the coordinates
that are used for interpolation and texture lookup.
texture object An opaque data structure used to
store all data related to a texture. A texture object
can include such things as an image, a mipmap, and
texture parameters (width, height, internal format,
resolution, wrapping modes, and so forth).
vertex A three-dimensional point. A set of vertices
specify the geometry of a shape. Vertices can have
a number of additional attributes such as color and
texture coordinates. See vertex array.
vertex array A data structure that stores a block of
data thatspecifiessuch things as vertex coordinates,
texture coordinates, surface normals, RGBA colors,
color indices, and edge flags.
virtual screen A combination of hardware, renderer,
and pixel format that OpenGL selects as suitable for
an imaging task. When the current virtual screen
changes, the current renderer typically changes.
Glossary
2012-07-23 | © 2004, 2012 Apple Inc. All Rights Reserved.
182Apple Inc.
© 2004, 2012 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Carbon, Cocoa, iChat,
Instruments, iPhoto, Logic, Mac, Macintosh,
Objective-C, OS X, Pages, Quartz, and Xcode are
trademarks of Apple Inc., registered in the U.S.
and other countries.
OpenCL is a trademark of Apple Inc.
DEC is a trademark of Digital Equipment
Corporation.
OpenGL is a registered trademark of Silicon
Graphics, Inc.
UNIX is a registered trademark of The Open
Group.
X Window System is a trademark of the
Massachusetts Institute of Technology.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
View Programming
Guide for iOSContents
About Windows and Views 7
At a Glance 7
Views Manage Your Application’s Visual Content 7
Windows Coordinate the Display of Your Views 8
Animations Provide the User with Visible Feedback for Interface Changes 8
The Role of Interface Builder 8
See Also 9
View and Window Architecture 10
View Architecture Fundamentals 10
View Hierarchies and Subview Management 11
The View Drawing Cycle 12
Content Modes 13
Stretchable Views 15
Built-In Animation Support 16
View Geometry and Coordinate Systems 17
The Relationship of the Frame, Bounds, and Center Properties 18
Coordinate System Transformations 20
Points Versus Pixels 21
The Runtime Interaction Model for Views 23
Tips for Using Views Effectively 25
Views Do Not Always Have a Corresponding View Controller 25
Minimize Custom Drawing 26
Take Advantage of Content Modes 26
Declare Views as Opaque Whenever Possible 26
Adjust Your View’s Drawing Behavior When Scrolling 26
Do Not Customize Controls by Embedding Subviews 27
Windows 28
Tasks That Involve Windows 28
Creating and Configuring a Window 29
Creating Windows in Interface Builder 29
Creating a Window Programmatically 30
Adding Content to Your Window 30
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
2Changing the Window Level 31
Monitoring Window Changes 31
Displaying Content on an External Display 32
Handling Screen Connection and Disconnection Notifications 33
Configuring a Window for an External Display 35
Configuring the Screen Mode of an External Display 37
Views 38
Creating and Configuring View Objects 38
Creating View Objects Using Interface Builder 39
Creating View Objects Programmatically 39
Setting the Properties of a View 40
Tagging Views for Future Identification 42
Creating and Managing a View Hierarchy 42
Adding and Removing Subviews 43
Hiding Views 46
Locating Views in a View Hierarchy 47
Translating, Scaling, and Rotating Views 47
Converting Coordinates in the View Hierarchy 50
Adjusting the Size and Position of Views at Runtime 51
Being Prepared for Layout Changes 51
Handling Layout Changes Automatically Using Autoresizing Rules 52
Tweaking the Layout of Your Views Manually 54
Modifying Views at Runtime 54
Interacting with Core Animation Layers 56
Changing the Layer Class Associated with a View 56
Embedding Layer Objects in a View 57
Defining a Custom View 58
Checklist for Implementing a Custom View 58
Initializing Your Custom View 59
Implementing Your Drawing Code 60
Responding to Events 62
Cleaning Up After Your View 63
Animations 64
What Can Be Animated? 64
Animating Property Changes in a View 66
Starting Animations Using the Block-Based Methods 66
Starting Animations Using the Begin/Commit Methods 68
Nesting Animation Blocks 72
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
3
ContentsImplementing Animations That Reverse Themselves 73
Creating Animated Transitions Between Views 73
Changing the Subviews of a View 74
Replacing a View with a Different View 76
Linking Multiple Animations Together 77
Animating View and Layer Changes Together 77
Document Revision History 80
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
4
ContentsFigures, Tables, and Listings
View and Window Architecture 10
Figure 1-1 Architecture of the views in a sample application 11
Figure 1-2 Content mode comparisons 14
Figure 1-3 Stretching the background of a button 15
Figure 1-4 Coordinate system orientation in UIKit 17
Figure 1-5 Relationship between a view's frame and bounds 19
Figure 1-6 Rotating a view and its content 21
Figure 1-7 UIKit interactions with your view objects 23
Table 1-1 Screen dimensions for iOS-based devices 22
Windows 28
Listing 2-1 Registering for screen connect and disconnect notifications 33
Listing 2-2 Handling connect and disconnect notifications 34
Listing 2-3 Configuring a window for an external display 35
Views 38
Figure 3-1 Layered views in the Clock application 43
Figure 3-2 Rotating a view 45 degrees 49
Figure 3-3 Converting values in a rotated view 51
Figure 3-4 View autoresizing mask constants 53
Table 3-1 Usage of some key view properties 40
Table 3-2 Autoresizing mask constants 52
Listing 3-1 Adding a view to a window 44
Listing 3-2 Adding views to an existing view hierarchy 45
Listing 3-3 Adding a custom layer to a view 57
Listing 3-4 Initializing a view subclass 59
Listing 3-5 A drawing method 61
Listing 3-6 Implementing the dealloc method 63
Animations 64
Table 4-1 Animatable UIView properties 64
Table 4-2 Methods for configuring animation blocks 69
Listing 4-1 Performing a simple block-based animation 66
Listing 4-2 Creating an animation block with custom options 67
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
5Listing 4-3 Performing a simple begin/commit animation 69
Listing 4-4 Configuring animation parameters using the begin/commit methods 70
Listing 4-5 Nesting animations that have different configurations 72
Listing 4-6 Swapping an empty text view for an existing one 74
Listing 4-7 Changing subviews using the begin/commit methods 75
Listing 4-8 Toggling between two views in a view controller 76
Listing 4-9 Mixing view and layer animations 78
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
6
Figures, Tables, and ListingsIn iOS, you use windows and views to present your application’s content on the screen. Windows do not have
any visible content themselves but provide a basic container for your application’s views. Views define a portion
of a window that you want to fill with some content. For example, you might have views that display images,
text, shapes, or some combination thereof. You can also use views to organize and manage other views.
At a Glance
Every application has at least one window and one view for presenting its content. UIKit and other system
frameworks provide predefined viewsthat you can use to present your content. These viewsrange from simple
buttons and text labels to more complex views such as table views, picker views, and scroll views. In places
where the predefined views do not provide what you need, you can also define custom views and manage
the drawing and event handling yourself.
Views Manage Your Application’s Visual Content
A view is an instance of the UIView class (or one of its subclasses) and manages a rectangular area in your
application window. Views are responsible for drawing content, handling multitouch events, and managing
the layout of any subviews. Drawing involves using graphics technologies such as Core Graphics, OpenGL ES,
or UIKit to draw shapes, images, and text inside a view’s rectangular area. A view responds to touch events in
its rectangular area either by using gesture recognizers or by handling touch events directly. In the view
hierarchy, parent views are responsible for positioning and sizing their child views and can do so dynamically.
This ability to modify child views dynamically lets your views adjust to changing conditions, such as interface
rotations and animations.
You can think of views as building blocks that you use to construct your user interface. Rather than use one
view to present all of your content, you often use several views to build a view hierarchy. Each view in the
hierarchy presents a particular portion of your user interface and is generally optimized for a specific type of
content. For example, UIKit has views specifically for presenting images, text and other types of content.
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
7
About Windows and ViewsRelevant Chapters: “View and Window Architecture” (page 10)
“Views” (page 38)
Windows Coordinate the Display of Your Views
A window is an instance of the UIWindow class and handles the overall presentation of your application’s user
interface. Windows work with views (and their owning view controllers) to manage interactions with, and
changes to, the visible view hierarchy. For the most part, your application’s window never changes. After the
window is created, it stays the same and only the views displayed by it change. Every application has at least
one window that displays the application’s user interface on a device’s main screen. If an external display is
connected to the device, applications can create a second window to present content on that screen as well.
Relevant Chapters: “Windows” (page 28)
Animations Provide the User with Visible Feedback for Interface Changes
Animations provide users with visible feedback about changes to your view hierarchy. The system defines
standard animationsfor presenting modal views and transitioning between different groups of views. However,
many attributes of a view can also be animated directly. For example, through animation you can change the
transparency of a view, its position on the screen, its size, its background color, or other attributes. And if you
work directly with the view’s underlying Core Animation layer object, you can perform many other animations
as well.
Relevant Chapters: “Animations” (page 64)
The Role of Interface Builder
Interface Builder is an application that you use to graphically construct and configure your application’s windows
and views. Using Interface Builder, you assemble your views and place them in a nib file, which is a resource
file that stores a freeze-dried version of your views and other objects. When you load a nib file at runtime, the
objects inside it are reconstituted into actual objects that your code can then manipulate programmatically.
Interface Builder greatly simplifiesthe work you have to do in creating your application’s user interface. Because
support for Interface Builder and nib files is incorporated throughout iOS, little effort is required to incorporate
nib files into your application’s design.
About Windows and Views
The Role of Interface Builder
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
8For more information about how to use Interface Builder, see Interface Builder User Guide . For information
about how view controllers manage the nib files containing their views, see “Custom View Controllers” in View
Controller Programming Guide for iOS .
See Also
Because views are very sophisticated and flexible objects, it would be impossible to cover all of their behaviors
in one document. However, other documents are available to help you learn about other aspects of managing
views and your user interface as a whole.
● View controllers are an important part of managing your application’s views. A view controller presides
over all of the viewsin a single view hierarchy and facilitatesthe presentation of those views on the screen.
For more information about view controllers and the role they play, see View Controller Programming
Guide for iOS .
● Views are the key recipients of gesture and touch events in your application. For more information about
using gesture recognizers and handling touch events directly, see Event Handling Guide for iOS .
● Custom views must use the available drawing technologies to render their content. For information about
using these technologies to draw within your views, see Drawing and Printing Guide for iOS .
●
In places where the standard view animations are notsufficient, you can use Core Animation. For information
about implementing animations using Core Animation, see Core Animation Programming Guide .
About Windows and Views
See Also
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
9Views and windows present your application’s user interface and handle the interactions with that interface.
UIKit and other system frameworks provide a number of views that you can use as-is with little or no
modification. You can also define custom views for places where you need to present content differently than
the standard views allow.
Whether you use the system views or create your own custom views, you need to understand the infrastructure
provided by the UIView and UIWindow classes. These classes provide sophisticated facilities for managing
the layout and presentation of views. Understanding how those facilities work is important for making sure
your views behave appropriately when changes occur in your application.
View Architecture Fundamentals
Most of the things you might want to do visually are done with view objects—instances of the UIView class.
A view object defines a rectangular region on the screen and handles the drawing and touch events in that
region. A view can also act as a parent for other views and coordinate the placement and sizing of those views.
The UIView class does most of the work in managing these relationships between views, but you can also
customize the default behavior as needed.
Views work in conjunction with Core Animation layers to handle the rendering and animating of a view’s
content. Every view in UIKit is backed by a layer object (usually an instance of the CALayer class), which
manages the backing store for the view and handles view-related animations. Most operations you perform
should be through the UIView interface. However, in situations where you need more control over the rendering
or animation behavior of your view, you can perform operations through its layer instead.
To understand the relationship between views and layers, it helps to look at an example. Figure 1-1 shows the
view architecture from the ViewTransitions sample application along with the relationship to the underlying
Core Animation layers. The views in the application include a window (which is also a view), a generic UIView
object that acts as a container view, an image view, a toolbar for displaying controls, and a bar button item
(which is not a view itself but which manages a view internally). (The actual ViewTransitions sample application
includes an additional image view that is used to implement transitions. For simplicity, and because that view
is usually hidden, it is not included in Figure 1-1.) Every view has a corresponding layer object that can be
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
10
View and Window Architectureaccessed from that view’s layer property. (Because a bar button item is not a view, you cannot access its layer
directly.) Behind those layer objects are Core Animation rendering objects and ultimately the hardware buffers
used to manage the actual bits on the screen.
Figure 1-1 Architecture of the views in a sample application
UIKit views Core Animation layers
UIImageView
UIView
UIWindow
UIToolbar UIBarButtonItem
(internal view)
The use of Core Animation layer objects has important implications for performance. The actual drawing code
of a view object is called as little as possible, and when the code is called, the results are cached by Core
Animation and reused as much as possible later. Reusing already-rendered content eliminates the expensive
drawing cycle usually needed to update views. Reuse of this content is especially important during animations,
where the existing content can be manipulated. Such reuse is much less expensive than creating new content.
View Hierarchies and Subview Management
In addition to providing its own content, a view can act as a container for other views. When one view contains
another, a parent-child relationship is created between the two views. The child view in the relationship is
known asthe subview and the parent view is known asthe superview. The creation of thistype of relationship
has implications for both the visual appearance of your application and the application’s behavior.
Visually, the content of a subview obscures all or part of the content of its parent view. If the subview is totally
opaque, then the area occupied by the subview completely obscures the corresponding area of the parent. If
the subview is partially transparent, the content from the two viewsis blended together prior to being displayed
View and Window Architecture
View Architecture Fundamentals
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
11on the screen. Each superview stores its subviews in an ordered array and the order in that array also affects
the visibility of each subview. If two sibling subviews overlap each other, the one that was added last (or was
moved to the end of the subview array) appears on top of the other.
The superview-subview relationship also impacts several view behaviors. Changing the size of a parent view
has a ripple effect that can cause the size and position of any subviews to change too. When you change the
size of a parent view, you can control the resizing behavior of each subview by configuring the view
appropriately. Other changes that affect subviews include hiding a superview, changing a superview’s alpha
(transparency), or applying a mathematical transform to a superview’s coordinate system.
The arrangement of views in a view hierarchy also determines how your application responds to events. When
a touch occurs inside a specific view, the system sends an event object with the touch information directly to
that view for handling. However, if the view does not handle a particular touch event, it can pass the event
object along to its superview. If the superview does not handle the event, it passes the event object to its
superview, and so on up the responder chain. Specific views can also pass the event object to an intervening
responder object, such as a view controller. If no object handles the event, it eventually reaches the application
object, which generally discards it.
For more information about how to create view hierarchies,see “Creating and Managing a View Hierarchy” (page
42).
The View Drawing Cycle
The UIView class uses an on-demand drawing model for presenting content. When a view first appears on
the screen, the system asks it to draw its content. The system captures a snapshot of this content and uses
that snapshot as the view’s visual representation. If you never change the view’s content, the view’s drawing
code may never be called again. The snapshot image is reused for most operations involving the view. If you
do change the content, you notify the system that the view has changed. The view then repeats the process
of drawing the view and capturing a snapshot of the new results.
When the contents of your view change, you do not redraw those changes directly. Instead, you invalidate the
view using either the setNeedsDisplay or setNeedsDisplayInRect: method. These methods tell the
system that the contents of the view changed and need to be redrawn at the next opportunity. The system
waits until the end of the current run loop before initiating any drawing operations. This delay gives you a
chance to invalidate multiple views, add or remove views from your hierarchy, hide views, resize views, and
reposition views all at once. All of the changes you make are then reflected at the same time.
View and Window Architecture
View Architecture Fundamentals
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
12Note: Changing a view’s geometry does not automatically cause the system to redraw the view’s
content. The view’s contentMode property determines how changes to the view’s geometry are
interpreted. Most content modes stretch or reposition the existing snapshot within the view’s
boundaries and do not create a new one. For more information about how content modes affect
the drawing cycle of your view, see “Content Modes” (page 13).
When the time comes to render your view’s content, the actual drawing process varies depending on the view
and its configuration. System views typically implement private drawing methods to render their content.
Those same system views often expose interfaces that you can use to configure the view’s actual appearance.
For custom UIView subclasses, you typically override the drawRect: method of your view and use that
method to draw your view’s content. There are also other ways to provide a view’s content, such as setting
the contents of the underlying layer directly, but overriding the drawRect: method is the most common
technique.
For more information about how to draw content for custom views, see “Implementing Your Drawing
Code” (page 60).
Content Modes
Each view has a content mode that controls how the view recycles its content in response to changes in the
view’s geometry and whether it recycles its content at all. When a view is first displayed, it renders its content
as usual and the results are captured in an underlying bitmap. After that, changes to the view’s geometry do
not always cause the bitmap to be recreated. Instead, the value in the contentMode property determines
whether the bitmap should be scaled to fit the new bounds or simply pinned to one corner or edge of the
view.
The content mode of a view is applied whenever you do the following:
● Change the width or height of the view’s frame or bounds rectangles.
● Assign a transform that includes a scaling factor to the view’s transform property.
View and Window Architecture
View Architecture Fundamentals
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
13By default, the contentMode property for most views is set to UIViewContentModeScaleToFill, which
causes the view’s contents to be scaled to fit the new frame size. Figure 1-2 shows the results that occur for
some content modes that are available. As you can see from the figure, not all content modes result in the
view’s bounds being filled entirely, and those that do might distort the view’s content.
Figure 1-2 Content mode comparisons
UIViewContentModeScaleToFill
Distorting
Nondistorting
UIViewContentModeScaleAspectFit
UIViewContentModeScaleAspectFill
Nondistorting
Nondistorting
UIViewContentModeLeft
View and Window Architecture
View Architecture Fundamentals
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
14Content modes are good for recycling the contents of your view, but you can also set the content mode to
the UIViewContentModeRedraw value when you specifically want your custom views to redraw themselves
during scaling and resizing operations. Setting your view’s content mode to this value forces the system to
call your view’s drawRect: method in response to geometry changes. In general, you should avoid using this
value whenever possible, and you should certainly not use it with the standard system views.
For more information about the available content modes, see UIView Class Reference .
Stretchable Views
You can designate a portion of a view asstretchable so that when the size of the view changes only the content
in the stretchable portion is affected. You typically use stretchable areas for buttons or other views where part
of the view defines a repeatable pattern. The stretchable area you specify can allow for stretching along one
or both axes of the view. Of course, when stretching a view along two axes, the edges of the view must also
define a repeatable pattern to avoid any distortion. Figure 1-3 shows how this distortion manifests itself in a
view. The color from each of the view’s original pixels is replicated to fill the corresponding area in the larger
view.
Figure 1-3 Stretching the background of a button
(0,0)
(1,1)
View and Window Architecture
View Architecture Fundamentals
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
15You specify the stretchable area of a view using the contentStretch property. This property accepts a
rectangle whose values are normalized to the range 0.0 to 1.0. When stretching the view, the system multiplies
these normalized values by the view’s current bounds and scale factor to determine which pixel or pixels need
to be stretched. The use of normalized values alleviates the need for you to update the contentStretch
property every time the bounds of your view change.
The view’s content mode also plays a role in determining how the view’s stretchable area is used. Stretchable
areas are only used when the content mode would cause the view’s content to be scaled. This means that
stretchable views are supported only with the UIViewContentModeScaleToFill,
UIViewContentModeScaleAspectFit, and UIViewContentModeScaleAspectFill content modes. If
you specify a content mode that pins the content to an edge or corner (and thus does not actually scale the
content), the view ignores the stretchable area.
Note: The use of the contentStretch property isrecommended over the creation of a stretchable
UIImage object when specifying the background for a view. Stretchable views are handled entirely
in the Core Animation layer, which typically offers better performance.
Built-In Animation Support
One of the benefits of having a layer object behind every view is that you can animate many view-related
changes easily. Animations are a useful way to communicate information to the user and should always be
considered during the design of your application. Many properties of the UIView class are animatable—that
is, semiautomatic support exists for animating from one value to another. To perform an animation for one of
these animatable properties, all you have to do is:
1. Tell UIKit that you want to perform an animation.
2. Change the value of the property.
Among the properties you can animate on a UIView object are the following:
frame—Use this to animate position and size changes for the view.
bounds—Use this to animate changes to the size of the view.
center—Use this to animate the position of the view.
transform—Use this to rotate or scale the view.
alpha—Use this to change the transparency of the view.
backgroundColor—Use this to change the background color of the view.
contentStretch—Use this to change how the view’s contents stretch.
View and Window Architecture
View Architecture Fundamentals
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
16One place where animations are very important is when transitioning from one set of viewsto another. Typically,
you use a view controller to manage the animations associated with major changes between parts of your
user interface. For example, for interfaces that involve navigating from higher-level to lower-level information,
you typically use a navigation controller to manage the transitions between the views displaying each successive
level of data. However, you can also create transitions between two sets of views using animations instead of
a view controller. You might do so in places where the standard view-controller animations do not yield the
results you want.
In addition to the animations you create using UIKit classes, you can also create animations using Core Animation
layers. Dropping down to the layer level gives you much more control over the timing and properties of your
animations.
For details about how to perform view-based animations, see “Animations” (page 64). For more information
about creating animations using Core Animation, see Core Animation Programming Guide and Core Animation
Cookbook .
View Geometry and Coordinate Systems
The default coordinate system in UIKit has its origin in the top-left corner and has axes that extend down and
to the right from the origin point. Coordinate values are represented using floating-point numbers, which
allow for precise layout and positioning of content regardless of the underlying screen resolution. Figure 1-4
shows this coordinate system relative to the screen. In addition to the screen coordinate system, windows and
views define their own local coordinate systems that allow you to specify coordinates relative to the view or
window origin instead of relative to the screen.
Figure 1-4 Coordinate system orientation in UIKit
y
x
(0,0)
View and Window Architecture
View Geometry and Coordinate Systems
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
17Because every view and window defines its own local coordinate system, you need to be aware of which
coordinate system is in effect at any given time. Every time you draw into a view or change its geometry, you
do so relative to some coordinate system. In the case of drawing, you specify coordinates relative to the view’s
own coordinate system. In the case of geometry changes, you specify coordinates relative to the superview’s
coordinate system. The UIWindow and UIView classes both include methods to help you convert from one
coordinate system to another.
Important: Some iOS technologies define default coordinate systems whose origin point and orientation
differ from those used by UIKit. For example, Core Graphics and OpenGL ES use a coordinate system whose
origin lies in the lower-left corner of the view or window and whose y-axis points upward relative to the
screen. Your code must take such differences into account when drawing or creating content and adjust
coordinate values (or the default orientation of the coordinate system) as needed.
The Relationship of the Frame, Bounds, and Center Properties
A view object tracks its size and location using its frame, bounds, and center properties:
● The frame property contains the frame rectangle, which specifies the size and location of the view in its
superview’s coordinate system.
● The bounds property contains the bounds rectangle, which specifies the size of the view (and its content
origin) in the view’s own local coordinate system.
● The center property contains the known center point of the view in the superview’s coordinate system.
You use the center and frame properties primarily for manipulating the geometry of the current view. For
example, you use these properties when building your view hierarchy or changing the position or size of a
view at runtime. If you are changing only the position of the view (and not its size), the center property is
the preferred way to do so. The value in the center property is always valid, even if scaling or rotation factors
have been added to the view’s transform. The same is not true for the value in the frame property, which is
considered invalid if the view’s transform is not equal to the identity transform.
You use the bounds property primarily during drawing. The bounds rectangle is expressed in the view’s own
local coordinate system. The default origin of this rectangle is (0, 0) and its size matches the size of the frame
rectangle. Anything you draw inside this rectangle is part of the view’s visible content. If you change the origin
of the boundsrectangle, anything you draw inside the new rectangle becomes part of the view’s visible content.
View and Window Architecture
View Geometry and Coordinate Systems
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
18Figure 1-5 shows the relationship between the frame and bounds rectangles for an image view. In the figure,
the upper-left corner of the image view is located at the point (40, 40) in its superview’s coordinate system
and the size of the rectangle is 240 by 380 points. For the bounds rectangle, the origin point is (0, 0) and the
size of the rectangle is similarly 240 by 380 points.
Figure 1-5 Relationship between a view's frame and bounds
Frame rectangle
Center
(160,230)
Bounds rectangle
240
240
380 380
(40,40) (0,0)
Although you can change the frame, bounds, and center properties independent of the others, changes to
one property affect the others in the following ways:
● When you set the frame property, the size value in the bounds property changes to match the new size
of the frame rectangle. The value in the center property similarly changes to match the new center point
of the frame rectangle.
● When you set the center property, the origin value in the frame changes accordingly.
● When you set the size of the bounds property, the size value in the frame property changes to match
the new size of the bounds rectangle.
By default, a view’s frame is not clipped to its superview’s frame. Thus, any subviews that lie outside of their
superview’sframe are rendered in their entirety. You can change this behavior, though, by setting the superview’s
clipsToBounds property to YES. Regardless of whether or not subviews are clipped visually, touch events
always respect the bounds rectangle of the target view’s superview. In other words, touch events occurring in
a part of a view that lies outside of its superview’s bounds rectangle are not delivered to that view.
View and Window Architecture
View Geometry and Coordinate Systems
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
19Coordinate System Transformations
Coordinate system transformations offer a way to alter your view (or its contents) quickly and easily. An affine
transform is a mathematical matrix that specifies how points in one coordinate system map to points in a
different coordinate system. You can apply affine transforms to your entire view to change the size, location,
or orientation of the view relative to its superview. You can also use affine transforms in your drawing code to
perform the same types of manipulations to individual pieces of rendered content. How you apply the affine
transform therefore depends on context:
● To modify your entire view, modify the affine transform in the transform property of your view.
● To modify specific pieces of content in your view’s drawRect: method, modify the affine transform
associated with the active graphics context.
You typically modify the transform property of a view when you want to implement animations. For example,
you could use this property to create an animation of your view rotating around its center point. You would
not use this property to make permanent changes to your view, such as modifying its position or size a view
within its superview’s coordinate space. For that type of change, you should modify the frame rectangle of
your view instead.
Note: When modifying the transform property of your view, all transformations are performed
relative to the center point of the view.
In your view’s drawRect: method, you use affine transforms to position and orient the items you plan to
draw. Rather than fix the position of an object at some location in your view, it is simpler to create each object
relative to a fixed point, typically (0, 0), and use a transform to position the object immediately prior to drawing.
That way, if the position of the object changes in your view, all you have to do is modify the transform, which
is much faster and less expensive than recreating the object at its new location. You can retrieve the affine
transform associated with a graphics context using the CGContextGetCTM function and you can use the
related Core Graphics functions to set or modify this transform during drawing.
The current transformation matrix (CTM) is the affine transform in use at any given time. When manipulating
the geometry of your entire view, the CTM is the affine transform stored in your view’s transform property.
Inside your drawRect: method, the CTM is the affine transform associated with the active graphics context.
The coordinate system of each subview builds upon the coordinate systems of its ancestors. So when you
modify a view’s transform property, that change affects the view and all of its subviews. However, these
changes affect only the final rendering of the views on the screen. Because each view draws its content and
lays out its subviews relative to its own bounds, it can ignore its superview’s transform during drawing and
layout.
View and Window Architecture
View Geometry and Coordinate Systems
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
20Figure 1-6 demonstrates how two different rotation factors combine visually when rendered. Inside the view’s
drawRect: method, applying a 45 degree rotation factor to a shape causes that shape to appear rotated by
45 degrees. Applying a separate 45 degree rotation factor to the view then causes the shape to appear to be
rotated by 90 degrees. The shape is still rotated by only 45 degrees relative to the view that drew it, but the
view rotation makes it appear to be rotated by more.
Figure 1-6 Rotating a view and its content
No rotations Shape rotated 45˚
during drawing
Shape and view each
rotated 45˚
Important: If a view’s transform property is not the identity transform, the value of that view’s frame
property is undefined and must be ignored. When applying transforms to a view, you must use the view’s
bounds and center properties to get the size and position of the view. The frame rectangles of any
subviews are still valid because they are relative to the view’s bounds.
For information about modifying your view’s transform property at runtime, see “Translating, Scaling, and
Rotating Views” (page 47). For information about how to use transforms to position content during drawing,
see Drawing and Printing Guide for iOS .
Points Versus Pixels
In iOS, all coordinate values and distances are specified using floating-point valuesin unitsreferred to as points.
The measurable size of a point variesfrom device to device and islargely irrelevant. The main thing to understand
about points is that they provide a fixed frame of reference for drawing.
View and Window Architecture
View Geometry and Coordinate Systems
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
21Table 1-1 lists the screen dimensions (measured in points) for different types of iOS-based devices in a portrait
orientation. The width dimension is listed first, followed by the height dimension of the screen. As long as you
design your interface to these screen sizes, your views will display correctly on the corresponding type of
device.
Table 1-1 Screen dimensions for iOS-based devices
Device Screen dimensions (in points)
iPhone and iPod touch 320 x 480
iPad 768 x 1024
The point-based measuring system used for each type of device defines what is known as the user coordinate
space. This is the standard coordinate space you use for nearly all of your code. For example, you use points
and the user coordinate space when manipulating the geometry of a view or calling Core Graphics functions
to draw the contents of your view. Although coordinates in the user coordinate space sometimes map directly
to the pixels on the device’s screen, you should never assume that this is the case. Instead, you should always
remember the following:
One point does not necessarily correspond to one pixel on the screen.
At the device level, all coordinates you specify in your view must be converted to pixels atsome point. However,
the mapping of pointsin the user coordinate space to pixelsin the device coordinate space is normally handled
by the system. Both UIKit and Core Graphics use a primarily vector-based drawing model where all coordinate
values are specified using points. Thus, if you draw a curve using Core Graphics, you specify the curve using
the same values, regardless of the resolution of the underlying screen.
When you need to work with images or other pixel-based technologies such as OpenGL ES, iOS provides help
in managing those pixels. For static image files stored as resources in your application bundle, iOS defines
conventionsforspecifying your images at different pixel densities and for loading the image that best matches
the current screen resolution. Views also provide information about the current scale factor so that you can
adjust any pixel-based drawing code manually to accommodate higher-resolution screens. The techniques for
dealing with pixel-based content at different screen resolutions is described in “Supporting High-Resolution
Screens” in Drawing and Printing Guide for iOS .
View and Window Architecture
View Geometry and Coordinate Systems
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
22The Runtime Interaction Model for Views
Any time a user interacts with your user interface, or any time your own code programmatically changes
something, a complex sequence of events takes place inside of UIKit to handle that interaction. At specific
points during that sequence, UIKit calls out to your view classes and gives them a chance to respond on behalf
of your application. Understanding these callout points is important to understanding where your views fit
into the system. Figure 1-7 shows the basic sequence of events that starts with the user touching the screen
and ends with the graphics system updating the screen content in response. The same sequence of events
would also occur for any programmatically initiated actions.
Figure 1-7 UIKit interactions with your view objects
Your Application
iPhone OS
Touches
• Buffers
• Images
• Attributes
• Geometry
• Animations
touches
layoutSubviews
drawRect
Compositor Draw images, text, etc.
Touch Framework
Graphics hardware
UIKit
setNeedsDisplay
frame, alpha, etc.
setNeedsLayout
setNeedsDisplay
frame, alpha, etc.
The following steps break the event sequence in Figure 1-7 (page 23) down even further and explain what
happens at each stage and how you might want your application to react in response.
1. The user touches the screen.
2. The hardware reports the touch event to the UIKit framework.
3. The UIKit framework packages the touch into a UIEvent object and dispatches it to the appropriate view.
(For a detailed explanation of how UIKit delivers events to your views, see Event Handling Guide for iOS .)
4. The event-handling code of your view responds to the event. For example, your code might:
● Change the properties (frame, bounds, alpha, and so on) of the view or its subviews.
● Call the setNeedsLayout method to mark the view (or its subviews) as needing a layout update.
● Call the setNeedsDisplay or setNeedsDisplayInRect: method to mark the view (or itssubviews)
as needing to be redrawn.
● Notify a controller about changes to some piece of data.
View and Window Architecture
The Runtime Interaction Model for Views
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
23Of course, it is up to you to decide which of these things the view should do and which methods it should
call.
5. If the geometry of a view changed for any reason, UIKit updates its subviews according to the following
rules:
a. If you have configured autoresizing rules for your views, UIKit adjusts each view according to those
rules. For more information about how autoresizing rules work, see “Handling Layout Changes
Automatically Using Autoresizing Rules” (page 52).
b. If the view implements the layoutSubviews method, UIKit calls it.
You can override this method in your custom views and use it to adjust the position and size of any
subviews. For example, a view that provides a large scrollable area would need to use severalsubviews
as “tiles” rather than create one large view, which is not likely to fit in memory anyway. In its
implementation of this method, the view would hide any subviewsthat are now offscreen or reposition
them and use them to draw newly exposed content. As part of this process, the view’s layout code
can also invalidate any views that need to be redrawn.
6. If any part of any view was marked as needing to be redrawn, UIKit asks the view to redraw itself.
For custom viewsthat explicitly define a drawRect: method, UIKit callsthat method. Your implementation
of this method should redraw the specified area of the view as quickly as possible and nothing else. Do
not make additional layout changes at this point and do not make other changes to your application’s
data model. The purpose of this method is to update the visual content of your view.
Standard system viewstypically do not implement a drawRect: method but instead manage their drawing
at this time.
7. Any updated views are composited with the rest of the application’s visible content and sent to the graphics
hardware for display.
8. The graphics hardware transfers the rendered content to the screen.
Note: The preceding update model applies primarily to applicationsthat use standard system views
and drawing techniques. Applications that use OpenGL ES for drawing typically configure a single
full-screen view and draw directly to the associated OpenGL graphics context. In such a case, the
view would still handle touch events but, because it is full-screen, it would not need to lay out
subviews or implement a drawRect: method. For more information about using OpenGL ES, see
OpenGL ES Programming Guide for iOS .
In the preceding set of steps, the primary integration points for your own custom views are:
● The event-handling methods:
● touchesBegan:withEvent:
View and Window Architecture
The Runtime Interaction Model for Views
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
24● touchesMoved:withEvent:
● touchesEnded:withEvent:
● touchesCancelled:withEvent:
● The layoutSubviews method
● The drawRect: method
These are the most commonly overridden methods for views but you may not need to override all of them. If
you use gesture recognizersto handle events, you do not need to override any of the event-handling methods.
Similarly, if your view does not contain subviews or its size does not change, there is no reason to override the
layoutSubviews method. Finally, the drawRect: method is needed only when the contents of your view
can change at runtime and you are using native technologiessuch as UIKit or Core Graphicsto do your drawing.
It is also important to remember that these are the primary integration points but not the only ones. Several
methods of the UIView class are designed to be override points for subclasses. You should look at the method
descriptions in UIView Class Reference to see which methods might be appropriate for you to override in your
custom implementations.
Tips for Using Views Effectively
Custom views are useful for situations where you need to draw something the standard system views do not
provide, but it is your responsibility to ensure that the performance of your views is good enough. UIKit does
everything it can to optimize view-related behaviors and help you achieve good performance in your custom
views. However, you can help UIKit in this aspect by considering the following tips.
Important: Before optimizing your drawing code, you should always gather data about your view’s current
performance. Measuring the current performance lets you confirm whether there actually is a problem and,
if there is, gives you a baseline measurement against which you can compare future optimizations.
Views Do Not Always Have a Corresponding View Controller
There is rarely a one-to-one relationship between individual views and view controllers in your application. The
job of a view controller is to manage a view hierarchy, which often consists of more than one view used to
implement some self-contained feature. For iPhone applications, each view hierarchy typically fills the entire
screen, although for iPad applications a view hierarchy may fill only part of the screen.
View and Window Architecture
Tips for Using Views Effectively
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
25As you design your application’s user interface, it is important to consider the role that view controllers will
play. View controllers provide a lot of important behaviors, such as coordinating the presentation of views on
the screen, coordinating the removal of those views from the screen, releasing memory in response to
low-memory warnings, and rotating views in response to interface orientation changes. Circumventing these
behaviors could cause your application to behave incorrectly or in unexpected ways.
For more information view controllers and their role in applications, see View Controller Programming Guide
for iOS .
Minimize Custom Drawing
Although custom drawing is necessary at times, it is also something you should avoid whenever possible. The
only time you should truly do any custom drawing is when the existing system view classes do not provide
the appearance or capabilities that you need. Any time your content can be assembled with a combination of
existing views, your best bet is to combine those view objects into a custom view hierarchy.
Take Advantage of Content Modes
Content modes minimize the amount of time spent redrawing your views. By default, views use the
UIViewContentModeScaleToFill content mode, which scales the view’s existing contents to fit the view’s
frame rectangle. You can change this mode as needed to adjust your content differently, but you should avoid
using the UIViewContentModeRedraw content mode if you can. Regardless of which content mode is in
effect, you can always force your view to redraw its contents by calling setNeedsDisplay or
setNeedsDisplayInRect:.
Declare Views as Opaque Whenever Possible
UIKit uses the opaque property of each view to determine whether the view can optimize compositing
operations. Setting the value of this property to YES for a custom view tells UIKit that it does not need to render
any content behind your view. Less rendering can lead to increased performance for your drawing code and
is generally encouraged. Of course, if you set the opaque property to YES, your view must fills its bounds
rectangle completely with fully opaque content.
Adjust Your View’s Drawing Behavior When Scrolling
Scrolling can incur numerous view updates in a short amount of time. If your view’s drawing code is not tuned
appropriately, scrolling performance for your view could be sluggish. Rather than trying to ensure that your
view’s content is pristine at all times, consider changing your view’s behavior when a scrolling operation begins.
View and Window Architecture
Tips for Using Views Effectively
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
26For example, you can reduce the quality of your rendered content temporarily or change the content mode
while a scroll isin progress. When scrolling stops, you can then return your view to its previousstate and update
the contents as needed.
Do Not Customize Controls by Embedding Subviews
Although it is technically possible to add subviews to the standard system controls—objects that inherit from
UIControl—you should never customize them in this way. Controlsthatsupport customizations do so through
explicit and well-documented interfaces in the control class itself. For example, the UIButton class contains
methods for setting the title and background images for the button. Using the defined customization points
meansthat your code will always work correctly. Circumventing these methods, by embedding a custom image
view or label inside the button, might cause your application to behave incorrectly now or at some point in
the future if the button’s implementation changes.
View and Window Architecture
Tips for Using Views Effectively
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
27Every iOS application needs at least one window—an instance of the UIWindow class—and some may include
more than one window. A window object has several responsibilities:
●
It contains your application’s visible content.
●
It plays a key role in the delivery of touch events to your views and other application objects.
●
It works with your application’s view controllers to facilitate orientation changes.
In iOS, windows do not have title bars, close boxes, or any other visual adornments. A window is always just
a blank container for one or more views. Also, applications do not change their content by showing new
windows. When you want to change the displayed content, you change the frontmost views of your window
instead.
Most iOS applications create and use only one window during their lifetime. This window spans the entire
main screen of the device and is loaded from the application’s main nib file (or created programmatically) early
in the life of the application. However, if an application supports the use of an external display for video out,
it can create an additional window to display content on that external display. All other windows are typically
created by the system, and are usually created in response to specific events, such as an incoming phone call.
Tasks That Involve Windows
For many applications, the only time the application interacts with its window is when it creates the window
at startup. However, you can use your application’s window object to perform a few application-related tasks:
● Use the window object to convert points and rectangles to or from the window’s local coordinate
system. For example, if you are provided with a value in window coordinates, you might want to convert
it to the coordinate system of a specific view before trying to use it. For information on how to convert
coordinates, see “Converting Coordinates in the View Hierarchy” (page 50).
● Use window notifications to track window-related changes. Windows generate notifications when they
are shown or hidden or when they accept or resign the key status. You can use these notifications to
perform actions in other parts of your application. For more information, see “Monitoring Window
Changes” (page 31).
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
28
WindowsCreating and Configuring a Window
You can create and configure your application’s main window programmatically or using Interface Builder. In
either case, you create the window at launch time and should retain it and store a reference to it in your
application delegate object. If your application creates additional windows, have the application create them
lazily when they are needed. For example, if your application supports displaying content on an external display,
it should wait until a display is connected before creating the corresponding window.
You should always create your application’s main window at launch time regardless of whether your application
is being launched into the foreground or background. Creating and configuring a window is not an expensive
operation by itself. However, if your application is launched straight into the background, you should avoid
making the window visible until your application enters the foreground.
Creating Windows in Interface Builder
Creating your application’s main window using Interface Builder issimple because the Xcode project templates
do it for you. Every new Xcode application project includes a main nib file (usually with the name
MainWindow.xib or some variant thereof) that includes the application’s main window. In addition, these
templates also define an outlet for that window in the application delegate object. You use this outlet to access
the window object in your code.
Important: When creating your window in Interface Builder, it is recommended that you enable the Full
Screen at Launch option in the attributes inspector. If this option is not enabled and your window is smaller
than the screen of the target device, touch events will not be received by some of your views. Thisis because
windows (like all views) do not receive touch events outside of their bounds rectangle. Because views are
not clipped to the window’s bounds by default, the views still appear visible but events do not reach them.
Enabling the Full Screen at Launch option ensures that the window is sized appropriately for the current
screen.
If you are retrofitting a project to use Interface Builder, creating a window using Interface Builder is a simple
matter of dragging a window object to your nib file. Of course, you should also do the following:
● To access the window at runtime, you should connect the window to an outlet, typically one defined in your
application delegate or the File’s Owner of the nib file.
●
If your retrofit plans include making your new nib file the main nib file of your application, you must also
set the NSMainNibFile key in your application’s Info.plist file to the name of your nib file. Changing
the value of this key ensures that the nib file is loaded and available for use by the time the
application:didFinishLaunchingWithOptions: method of your application delegate is called.
For more information about creating and configuring nib files,see Interface Builder User Guide . For information
about how to load nib files into your application at runtime, see “Nib Files” in Resource Programming Guide .
Windows
Creating and Configuring a Window
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
29Creating a Window Programmatically
If you prefer to create your application’s main window programmatically, you should include code similar to
the following in the application:didFinishLaunchingWithOptions: method of your application
delegate:
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]
autorelease];
In the preceding example, self.window is assumed to be a declared property of your application delegate
that is configured to retain the window object. If you were creating a window for an external display instead,
you would assign it to a different variable and you would need to specify the bounds of the non main UIScreen
object representing that display.
When creating windows, you should always set the size of the window to the full bounds of the screen. You
should not reduce the size of the window to accommodate the status bar or any other items. The status bar
always floats on top of the window anyway, so the only thing you should shrink to accommodate the status
bar is the view you put into your window. And if you are using view controllers, the view controller should
handle the sizing of your views automatically.
Adding Content to Your Window
Each window typically has a single root view object (managed by a corresponding view controller) that contains
all of the other views representing your content. Using a single root view simplifies the process of changing
your interface; to display new content, all you have to do is replace the root view. To install a view in your
window, use the addSubview: method. For example, to install a view that is managed by a view controller,
you would use code similar to the following:
[window addSubview:viewController.view];
In place of the preceding code, you can alternatively configure the rootViewController property of the
window in your nib file. This property offers a convenient way to configure the root view of the window using
a nib file instead of programmatically. If this property is set when the window is loaded from its nib file, UIKit
automatically installsthe view from the associated view controller asthe root view of the window. This property
is used only to install the root view and is not used by the window to communicate with the view controller.
You can use any view you want for a window’s root view. Depending on your interface design, the root view
can be a generic UIView object that acts as a container for one or more subviews, the root view can be a
standard system view, or the root view can be a custom view that you define. Some standard system views
that are commonly used as root views include scroll views, table views, and image views.
Windows
Creating and Configuring a Window
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
30When configuring the root view of the window, you are responsible for setting its initial size and position
within the window. For applications that do not include a status bar, or that display a translucent status bar,
set the view size to match the size of the window. For applications that show an opaque status bar, position
your view below the status bar and reduce its size accordingly. Subtracting the status bar height from the
height of your view prevents the top portion of your view from being obscured.
Note: If the root view of your window is provided by a container view controller (such as a tab bar
controller, navigation controller, or split-view controller), you do not need to set the initial size of
the view yourself. The container view controller automatically sizes its view appropriately based on
whether the status bar is visible.
Changing the Window Level
Each UIWindow object has a configurable windowLevel property that determines how that window is
positioned relative to other windows. For the most part, you should not need to change the level of your
application’s windows. New windows are automatically assigned to the normal window level at creation time.
The normal window level indicates that the window presents application-related content. Higher window
levels are reserved for information that needs to float above the application content, such as the system status
bar or alert messages. And although you can assign windows to these levels yourself, the system usually does
this for you when you use specific interfaces. For example, when you show or hide the status bar or display an
alert view, the system automatically creates the needed windows to display those items.
Monitoring Window Changes
If you want to track the appearance or disappearance of windows inside your application, you can do so using
these window-related notifications:
● UIWindowDidBecomeVisibleNotification
● UIWindowDidBecomeHiddenNotification
● UIWindowDidBecomeKeyNotification
● UIWindowDidResignKeyNotification
These notifications are delivered in response to programmatic changes in your application’s windows. Thus,
when your application shows or hides a window, the UIWindowDidBecomeVisibleNotification and
UIWindowDidBecomeHiddenNotification notifications are delivered accordingly. These notifications are
not delivered when your application moves into the background execution state. Even though your window
is not displayed on the screen while your application is in the background, it is still considered visible within
the context of your application.
Windows
Monitoring Window Changes
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
31The UIWindowDidBecomeKeyNotification and UIWindowDidResignKeyNotification notifications
help your application keep track of which window is the key window—that is, which window is currently
receiving keyboard events and other non touch-related events. Whereas touch events are delivered to the
window in which the touch occurred, events that do not have an associated coordinate value are delivered to
the key window of your application. Only one window at a time may be key.
Displaying Content on an External Display
To display content on an external display, you must create an additional window for your application and
associate it with the screen object representing the external display. New windows are normally associated
with the main screen by default. Changing the window’s associated screen object causes the contents of that
window to be rerouted to the corresponding display. Once the window is associated with the correct screen,
you can add views to it and show it just like you do for your application’s main screen.
The UIScreen class maintains a list of screen objects representing the available hardware displays. Normally,
there is only one screen object representing the main display for any iOS-based device, but devicesthatsupport
connecting to an external display can have an additional screen object available. Devices that support an
external display include iPhone and iPod touch devices that have Retina displays and the iPad. Older devices,
such as iPhone 3GS, do not support external displays.
Note: Because external displays are essentially a video-out connection, you should not expect touch
events for views and controls in a window that is associated with an external display. In addition, it
is your application’s responsibility to update the contents of the window as needed. Thus, to mirror
the contents of your main window, your application would need to create a duplicate set of views
for the external display’s window and update them in tandem with the views in your main window.
The process for displaying content on an external display is described in the following sections. However, the
following steps summarize the basic process:
1. At application startup, register for the screen connection and disconnection notifications.
2. When it is time to display content on the external display, create and configure a window.
● Use the screens property of UIScreen to obtain the screen object for the external display.
● Create a UIWindow object and size it appropriately for the screen (or for your content).
● Assign the UIScreen object for the external display to the screen property of the window.
● Adjust the resolution of the screen object as needed to support your content.
● Add any appropriate views to the window.
Windows
Displaying Content on an External Display
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
323. Show the window and update it normally.
Handling Screen Connection and Disconnection Notifications
Screen connection and disconnection notifications are crucial for handling changes to external displays
gracefully. When the user connects or disconnects a display, the system sends appropriate notifications to
your application. You should use these notifications to update your application state and create or release the
window associated with the external display.
The important thing to remember about the connection and disconnection notificationsisthat they can come
at any time, even when your application is suspended in the background. Therefore, it is best to observe the
notifications from an object that is going to exist for the duration of your application’s runtime, such as your
application delegate. If your application is suspended, the notifications are queued until your application exits
the suspended state and starts running in either the foreground or background.
Listing 2-1 shows the code used to register for connection and disconnection notifications. This method is
called by the application delegate at initialization time but you could register for these notificationsfrom other
places in your application, too. The implementation of the handler methods is shown in Listing 2-2 (page 34).
Listing 2-1 Registering for screen connect and disconnect notifications
- (void)setupScreenConnectionNotificationHandlers
{
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(handleScreenConnectNotification:)
name:UIScreenDidConnectNotification object:nil];
[center addObserver:self selector:@selector(handleScreenDisconnectNotification:)
name:UIScreenDidDisconnectNotification object:nil];
}
If your application is active when an external display is attached to the device, itshould create a second window
for that display and fill it with some content. The content does not need to be the final content you want to
present. For example, if your application is not ready to use the extra screen, it can use the second window to
display some placeholder content. If you do not create a window for the screen, or if you create a window but
do not show it, a black field is displayed on the external display.
Windows
Displaying Content on an External Display
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
33Listing 2-2 shows how to create a secondary window and fill it with some content. In this example, the application
creates the window in the handler methods it uses to receive screen connection notifications. (For information
about registering for connection and disconnection notifications, see Listing 2-1 (page 33).) The handler
method for the connection notification creates a secondary window, associates it with the newly connected
screen and calls a method of the application’s main view controller to add some content to the window and
show it. The handler method for the disconnection notification releases the window and notifies the main
view controller so that it can adjust its presentation accordingly.
Listing 2-2 Handling connect and disconnect notifications
- (void)handleScreenConnectNotification:(NSNotification*)aNotification
{
UIScreen* newScreen = [aNotification object];
CGRect screenBounds = newScreen.bounds;
if (!_secondWindow)
{
_secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
_secondWindow.screen = newScreen;
// Set the initial UI for the window.
[viewController displaySelectionInSecondaryWindow:_secondWindow];
}
}
- (void)handleScreenDisconnectNotification:(NSNotification*)aNotification
{
if (_secondWindow)
{
// Hide and then delete the window.
_secondWindow.hidden = YES;
[_secondWindow release];
_secondWindow = nil;
// Update the main screen based on what is showing here.
[viewController displaySelectionOnMainScreen];
Windows
Displaying Content on an External Display
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
34}
}
Configuring a Window for an External Display
To display a window on an external screen, you must associate it with the correct screen object. This process
involves locating the proper UIScreen object and assigning it to the window’s screen property. You can get
the list of screen objects from the screens class method of UIScreen. The array returned by this method
always contains at least one object representing the main screen. If a second object is present, that object
represents a connected external display.
Listing 2-3 shows a method that is called at application startup to see if an external display is already attached.
If it is, the method creates a window, associatesit with the external display, and addssome placeholder content
before showing the window. In this case, the placeholder content is a white background and a label indicating
that there is no content to display. To show the window, this method changes the value of its hidden property
rather than calling makeKeyAndVisible. It does this because the window contains only static content and
is not used to handle events.
Listing 2-3 Configuring a window for an external display
- (void)checkForExistingScreenAndInitializeIfPresent
{
if ([[UIScreen screens] count] > 1)
{
// Associate the window with the second screen.
// The main screen is always at index 0.
UIScreen* secondScreen = [[UIScreen screens] objectAtIndex:1];
CGRect screenBounds = secondScreen.bounds;
_secondWindow = [[UIWindow alloc] initWithFrame:screenBounds];
_secondWindow.screen = secondScreen;
// Add a white background to the window
UIView* whiteField = [[UIView alloc] initWithFrame:screenBounds];
whiteField.backgroundColor = [UIColor whiteColor];
[_secondWindow addSubview:whiteField];
Windows
Displaying Content on an External Display
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
35[whiteField release];
// Center a label in the view.
NSString* noContentString = [NSString stringWithFormat:@""];
CGSize stringSize = [noContentString sizeWithFont:[UIFont
systemFontOfSize:18]];
CGRect labelSize = CGRectMake((screenBounds.size.width -
stringSize.width) / 2.0,
(screenBounds.size.height - stringSize.height)
/ 2.0,
stringSize.width, stringSize.height);
UILabel* noContentLabel = [[UILabel alloc] initWithFrame:labelSize];
noContentLabel.text = noContentString;
noContentLabel.font = [UIFont systemFontOfSize:18];
[whiteField addSubview:noContentLabel];
// Go ahead and show the window.
_secondWindow.hidden = NO;
}
}
Important: You should always associate a screen with a window before showing the window. While it is
possible to change screens for a window that is currently visible, doing so is an expensive operation and
should be avoided.
Assoon asthe window for an externalscreen is displayed, your application can begin updating it like any other
window. You can add and remove subviews as needed, change the contents of subviews, animate changes
to the views, and invalidate their contents as needed.
Windows
Displaying Content on an External Display
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
36Configuring the Screen Mode of an External Display
Depending on your content, you might want to change the screen mode before associating your window with
it. Many screens support multiple resolutions, some of which use different pixel aspect ratios. Screen objects
use the most common screen mode by default, but you can change that mode to one that is more suitable
for your content. For example, if you are implementing a game using OpenGL ES and your textures are designed
for a 640 x 480 pixel screen, you might change the screen mode for screens with higher default resolutions.
If you plan to use a screen mode other than the default one, you should apply that mode to the UIScreen
object before associating the screen with a window. The UIScreenMode class definesthe attributes of a single
screen mode. You can get a list of the modes supported by a screen from its availableModes property and
iterate through the list for one that matches your needs.
For more information about screen modes, see UIScreenMode Class Reference .
Windows
Displaying Content on an External Display
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
37Because view objects are the main way your application interacts with the user, they have many responsibilities.
Here are just a few:
● Layout and subview management
● A view defines its own default resizing behaviors in relation to its parent view.
● A view can manage a list of subviews.
● A view can override the size and position of its subviews as needed.
● A view can convert points in its coordinate system to the coordinate systems of other views or the
window.
● Drawing and animation
● A view draws content in its rectangular area.
● Some view properties can be animated to new values.
● Event handling
● A view can receive touch events.
● A view participates in the responder chain.
This chapter focuses on the steps for creating, managing, and drawing views and for handling the layout and
management of view hierarchies. For information about how to handle touch events (and other events) in
your views, see Event Handling Guide for iOS .
Creating and Configuring View Objects
You create views as self-contained objects either programmatically or using Interface Builder, and then you
assemble them into view hierarchies for use.
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
38
ViewsCreating View Objects Using Interface Builder
The simplest way to create viewsisto assemble them graphically using Interface Builder. From Interface Builder,
you can add views to your interface, arrange those views into hierarchies, configure each view’s settings, and
connect view-related behaviors to your code. Because Interface Builder uses live view objects—that is, actual
instances of the view classes—what you see at design time is what you get at runtime. You then save those
live objects in a nib file, which is a resource file that preserves the state and configuration of your objects.
You usually create nib filesin order to store an entire view hierarchy for one of your application’s view controllers.
The top level of the nib file usually contains a single view object that represents your view controller’s view.
(The view controller itself is typically represented by the File’s Owner object.) The top-level view should be
sized appropriately for the target device and contain all of the other views that are to be presented. It is rare
to use a nib file to store only a portion of your view controller’s view hierarchy.
When using nib files with a view controller, all you have to do is initialize the view controller with the nib file
information. The view controller handles the loading and unloading of your views at the appropriate times.
However, if your nib file is not associated with a view controller, you can load the nib file contents manually
using an NSBundle or UINib object, which use the data in the nib file to reconstitute your view objects.
For more information about how to use Interface Builder to create and configure your views, see Interface
Builder User Guide . For information about how view controllers load and manage their associated nib files, see
“Custom View Controllers” in View Controller Programming Guide for iOS . For more information about how to
load views programmatically from a nib file, see “Nib Files” in Resource Programming Guide .
Creating View Objects Programmatically
If you prefer to create views programmatically, you can do so using the standard allocation/initialization pattern.
The default initialization method for views is the initWithFrame: method, which sets the initial size and
position of the view relative to its (soon-to-be-established) parent view. For example, to create a new generic
UIView object, you could use code similar to the following:
CGRect viewRect = CGRectMake(0, 0, 100, 100);
UIView* myView = [[UIView alloc] initWithFrame:viewRect];
Views
Creating and Configuring View Objects
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
39Note: Although all views support the initWithFrame: method, some may have a preferred
initialization method that you should use instead. For information about any custom initialization
methods, see the reference documentation for the class.
After you create a view, you must add it to a window (or to another view in a window) before it can become
visible. For information on how to add viewsto your view hierarchy,see “Adding and Removing Subviews” (page
43).
Setting the Properties of a View
The UIView class hasseveral declared properties for controlling the appearance and behavior of the view. These
properties are for manipulating the size and position of the view, the view’stransparency, its background color,
and its rendering behavior. All of these properties have appropriate default values that you can change later
as needed. You can also configure many of these propertiesfrom Interface Builder using the Inspector window.
Table 3-1 lists some of the more commonly used properties (and some methods) and describes their usage.
Related properties are listed together so that you can see the options you have for affecting certain aspects
of the view.
Table 3-1 Usage of some key view properties
Properties Usage
These properties affect the opacity of the view. The alpha and hidden
properties change the view’s opacity directly.
The opaque property tells the system how it should composite your
view. Set this property to YES if your view’s content is fully opaque and
therefore does not reveal any of the underlying view’s content. Setting
this property to YES improves performance by eliminating unnecessary
compositing operations.
alpha, hidden, opaque
Views
Creating and Configuring View Objects
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
40Properties Usage
These properties affect the size and position of the view. The center
and frame properties represent the position of the view relative to its
parent view. The frame also includes the size of the view. The bounds
property defines the view’s visible content area in its own coordinate
system.
The transform property is used to animate or move the entire view
in complex ways. For example, you would use a transform to rotate or
scale the view. If the current transform is not the identity transform, the
frame property is undefined and should be ignored.
For information about the relationship between the bounds, frame,
and center properties, see “The Relationship of the Frame, Bounds,
and Center Properties” (page 18). For information about how transforms
affect a view, see “Coordinate System Transformations” (page 20).
bounds, frame, center,
transform
These properties affect the automatic resizing behavior of the view and
its subviews. The autoresizingMask property controls how a view
responds to changes in its parent view’s bounds. The
autoresizesSubviews property controls whether the current view’s
subviews are resized at all.
autoresizingMask,
autoresizesSubviews
These properties affect the rendering behavior of content inside the
view. The contentMode and contentStretch properties determine
how the content is treated when the view’s width or height changes.
The contentScaleFactor property is used only when you need to
customize the drawing behavior of your view for high-resolution screens.
For more information on how the content mode affects your view, see
“Content Modes” (page 13). For information about how the content
stretch rectangle affects your view, see “Stretchable Views” (page 15).
For information about how to handle scale factors, see “Supporting
High-Resolution Screens” in Drawing and Printing Guide for iOS .
contentMode,
contentStretch,
contentScaleFactor
These properties affect how your view processes touch events. The
gestureRecognizers property contains gesture recognizers attached
to the view. The other properties control what touch events the view
supports.
For information about how to respond to eventsin your views,see Event
Handling Guide for iOS .
gestureRecognizers,
userInteractionEnabled,
multipleTouchEnabled,
exclusiveTouch
Views
Creating and Configuring View Objects
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
41Properties Usage
These properties and methods help you manage the actual content of
your view. For simple views, you can set a background color and add
one ormore subviews. The subviews property itself contains a read-only
list of subviews, but there are several methods for adding and
rearranging subviews. For views with custom drawing behavior, you
must override the drawRect: method.
For more advanced content, you can work directly with the view’s Core
Animation layer. To specify an entirely different type of layer for the
view (such as a layer that supports OpenGL ES drawing calls), you must
override the layerClass method.
backgroundColor,
subviews, drawRect:
method, layer,
(layerClass method)
Forinformation aboutthe basic properties common to all views,seeUIViewClass Reference . Formore information
about specific properties of a view, see the reference documentation for that view.
Tagging Views for Future Identification
The UIView class contains a tag property that you can use to tag individual view objects with an integer value.
You can use tags to uniquely identify views inside your view hierarchy and to perform searches for those views
at runtime. (Tag-based searches are faster than iterating the view hierarchy yourself.) The default value for the
tag property is 0.
To search for a tagged view, use the viewWithTag: method of UIView. This method performs a depth-first
search of the receiver and its subviews. It does not search superviews or other parts of the view hierarchy.
Thus, calling this method from the root view of a hierarchy searches all views in the hierarchy but calling it
from a specific subview searches only a subset of views.
Creating and Managing a View Hierarchy
Managing view hierarchies is a crucial part of developing your application’s user interface. The organization
of your views influences both the visual appearance of your application and how your application responds
to changes and events. For example, the parent-child relationships in the view hierarchy determine which
objects might handle a specific touch event. Similarly, parent-child relationships define how each view responds
to interface orientation changes.
Views
Creating and Managing a View Hierarchy
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
42Figure 3-1 shows an example of how the layering of views creates the desired visual effect for an application.
In the case of the Clock application, the view hierarchy is composed of a mixture of views derived from different
sources. The tab bar and navigation views are special view hierarchies provided by the tab bar and navigation
controller objects to manage portions of the overall user interface. Everything between those bars belongs to
the custom view hierarchy that the Clock application provides.
Figure 3-1 Layered views in the Clock application
Window
Tab bar view
Navigation view
Custom view hierarchy
There are several ways to build view hierarchies in iOS applications, including graphically in Interface Builder
and programmatically in your code. The following sections show you how to assemble your view hierarchies
and, having done that, how to find views in the hierarchy and convert between different view coordinate
systems.
Adding and Removing Subviews
Interface Builder is the most convenient way to build view hierarchies because you assemble your views
graphically, see the relationships between the views, and see exactly how those views will appear at runtime.
When using Interface Builder, you save your resulting view hierarchy in a nib file, which you load at runtime as
the corresponding views are needed.
If you prefer to create your views programmatically instead, you create and initialize them and then use the
following methods to arrange them into hierarchies:
Views
Creating and Managing a View Hierarchy
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
43● To add a subview to a parent, call the addSubview: method of the parent view. This method adds the
subview to the end of the parent’s list of subviews.
● To insert a subview in the middle of the parent’s list of subviews, call any of the insertSubview:...
methods of the parent view. Inserting a subview in the middle of the list visually places that view behind
any views that come later in the list.
● To reorder existing subviewsinside their parent, callthe bringSubviewToFront:, sendSubviewToBack:,
or exchangeSubviewAtIndex:withSubviewAtIndex: methods of the parent view. Using these
methods is faster than removing the subviews and reinserting them.
● To remove a subview from its parent, call the removeFromSuperview method of the subview (not the
parent view).
When adding a subview to its parent, the subview’s current frame rectangle denotes its initial position inside
the parent view. A subview whose frame lies outside of its superview’s visible bounds is not clipped by default.
If you want yoursubview to be clipped to the superview’s bounds, you must explicitly set the clipsToBounds
property of the superview to YES.
The most common example of adding a subview to another view occurs in the
application:didFinishLaunchingWithOptions: method of almost every application. Listing 3-1 shows
a version of this method that installs the view from the application’s main view controller into the application
window. Both the window and the view controller are stored in the application’s main nib file, which is loaded
before the method is called. However, the view hierarchy managed by the view controller is not actually loaded
until the view property is accessed.
Listing 3-1 Adding a view to a window
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Add the view controller's view to the window and display.
[window addSubview:viewController.view];
[window makeKeyAndVisible];
return YES;
}
Views
Creating and Managing a View Hierarchy
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
44Another common place where you might add subviewsto a view hierarchy isin the loadView or viewDidLoad
methods of a view controller. If you are building your views programmatically, you put your view creation code
in the loadView method of your view controller. Whether you create your views programmatically or load
them from a nib file, you could include additional view configuration code in the viewDidLoad method.
Listing 3-2 showsthe viewDidLoad method of the TransitionsViewController classfrom the UICatalog
sample application. The TransitionsViewController class manages the animations associated with
transitioning between two views. The application’s initial view hierarchy (consisting of a root view and toolbar)
is loaded from a nib file. The code in the viewDidLoad method subsequently creates the container view and
image views used to manage the transitions. The purpose of the container view is to simplify the code needed
to implement the transition animations between the two image views. The container view has no real content
of its own.
Listing 3-2 Adding views to an existing view hierarchy
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = NSLocalizedString(@"TransitionsTitle", @"");
// create the container view which we will use for transition animation (centered
horizontally)
CGRect frame = CGRectMake(round((self.view.bounds.size.width - kImageWidth) /
2.0),
kTopPlacement, kImageWidth,
kImageHeight);
self.containerView = [[[UIView alloc] initWithFrame:frame] autorelease];
[self.view addSubview:self.containerView];
// The container view can represent the images for accessibility.
[self.containerView setIsAccessibilityElement:YES];
[self.containerView setAccessibilityLabel:NSLocalizedString(@"ImagesTitle",
@"")];
// create the initial image view
frame = CGRectMake(0.0, 0.0, kImageWidth, kImageHeight);
self.mainView = [[[UIImageView alloc] initWithFrame:frame] autorelease];
self.mainView.image = [UIImage imageNamed:@"scene1.jpg"];
Views
Creating and Managing a View Hierarchy
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
45[self.containerView addSubview:self.mainView];
// create the alternate image view (to transition between)
CGRect imageFrame = CGRectMake(0.0, 0.0, kImageWidth, kImageHeight);
self.flipToView = [[[UIImageView alloc] initWithFrame:imageFrame] autorelease];
self.flipToView.image = [UIImage imageNamed:@"scene2.jpg"];
}
Important: Superviews automatically retain their subviews, so after embedding a subview it is safe to
release that subview. In fact, doing so is recommended because it prevents your application from retaining
the view one time too many and causing a memory leak later. Just remember that if you remove a subview
from its superview and intend to reuse it, you must retain the subview again. The removeFromSuperview
method autoreleases a subview before removing it from its superview. If you do not retain the view before
the next event loop cycle, the view will be released.
Formore information about Cocoamemorymanagement conventions,see AdvancedMemoryManagement
Programming Guide .
When you add a subview to another view, UIKit notifies both the parent and child views of the change. If you
implement custom views, you can intercept these notifications by overriding one or more of the
willMoveToSuperview:, willMoveToWindow:, willRemoveSubview:, didAddSubview:,
didMoveToSuperview, or didMoveToWindow methods. You can use these notifications to update any state
information related to your view hierarchy or to perform additional tasks.
After creating a view hierarchy, you can navigate it programmatically using the superview and subviews
properties of your views. The window property of each view containsthe window in which that view is currently
displayed (if any). Because the root view in a view hierarchy has no parent, its superview property is set to
nil. For views that are currently onscreen, the window object is the root view of the view hierarchy.
Hiding Views
To hide a view visually, you can either set its hidden property to YES or change its alpha property to 0.0. A
hidden view does not receive touch events from the system. However, hidden views do participate in
autoresizing and other layout operations associated with the view hierarchy. Thus, hiding a view is often a
convenient alternative to removing views from your view hierarchy, especially if you plan to show the views
again at some point soon.
Views
Creating and Managing a View Hierarchy
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
46Important: If you hide a view that is currently the first responder, the view does not automatically resign
its first responder status. Events targeted at the first responder are still delivered to the hidden view. To
prevent this from happening, you should force your view to resign the first responder status when you hide
it. For more information about the responder chain, see Event Handling Guide for iOS .
If you want to animate a view’s transition from visible to hidden (or the reverse), you must do so using the
view’s alpha property. The hidden property is not an animatable property, so any changes you make to it
take effect immediately.
Locating Views in a View Hierarchy
There are two ways to locate views in a view hierarchy:
● Store pointers to any relevant views in an appropriate location, such as in the view controller that owns
the views.
● Assign a unique integer to each view’s tag property and use the viewWithTag: method to locate it.
Storing references to relevant views is the most common approach to locating views and makes accessing
those views very convenient. If you used Interface Builder to create your views, you can connect objects in
your nib file (including the File’s Owner object that represents the managing controller object) to one another
using outlets. For views you create programmatically, you can store referencesto those viewsin private member
variables. Whether you use outlets or private member variables, you are responsible for retaining the views as
needed and then releasing them as well. The best way to ensure objects are retained and released properly is
to use declared properties.
Tags are a useful way to reduce hard-coded dependencies and support more dynamic and flexible solutions.
Rather than storing a pointer to a view, you could locate it using its tag. Tags are also a more persistent way
of referring to views. For example, if you wanted to save the list of views that are currently visible in your
application, you would write out the tags of each visible view to a file. This is simpler than archiving the actual
view objects, especially in situations where you are tracking only which views are currently visible. When your
application is subsequently loaded, you would then re-create your views and use the saved list of tags to set
the visibility of each view, and thereby return your view hierarchy to its previous state.
Translating, Scaling, and Rotating Views
Every view has an associated affine transform that you can use to translate, scale, or rotate the view’s content.
View transforms alter the final rendered appearance of the view and are often used to implement scrolling,
animations, or other visual effects.
Views
Creating and Managing a View Hierarchy
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
47The transform property of UIView contains a CGAffineTransform structure with the transformations to
apply. By default, this property is set to the identity transform, which does not modify the appearance of the
view. You can assign a new transform to this property at any time. For example, to rotate a view by 45 degrees,
you could use the following code:
// M_PI/4.0 is one quarter of a half circle, or 45 degrees.
CGAffineTransform xform = CGAffineTransformMakeRotation(M_PI/4.0);
self.view.transform = xform;
Views
Creating and Managing a View Hierarchy
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
48Applying the transform in the preceding code to a view would rotate that view clockwise about its center
point. Figure 3-2 shows how this transformation would look if it were applied to an image view embedded in
an application.
Figure 3-2 Rotating a view 45 degrees
Unrotated Rotated 45˚
When applying multiple transformations to a view, the order in which you add those transformations to the
CGAffineTransform structure is significant. Rotating the view and then translating it is not the same as
translating the view and then rotating it. Even if the amounts of rotation and translation are the same in each
case, the sequence of the transformations affects the final results. In addition, any transformations you add
are applied to the view relative to its center point. Thus, applying a rotation factor rotates the view around its
center point. Scaling a view changes the width and height of the view but does not change its center point.
For more information about creating and using affine transforms, see “Transforms” in Quartz 2D Programming
Guide .
Views
Creating and Managing a View Hierarchy
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
49Converting Coordinates in the View Hierarchy
At various times, particularly when handling events, an application may need to convert coordinate values
from one frame of reference to another. For example, touch events report the location of each touch in the
window’s coordinate system but view objects often need that information in the view’slocal coordinate system.
The UIView class defines the following methods for converting coordinates to and from the view’s local
coordinate system:
convertPoint:fromView:
convertRect:fromView:
convertPoint:toView:
convertRect:toView:
The convert...:fromView: methods convert coordinates from some other view’s coordinate system to
the local coordinate system (bounds rectangle) of the current view. Conversely, the convert...:toView:
methods convert coordinates from the current view’s local coordinate system (bounds rectangle) to the
coordinate system of the specified view. If you specify nil as the reference view for any of the methods, the
conversions are made to and from the coordinate system of the window that contains the view.
In addition to the UIView conversion methods, the UIWindow class also defines several conversion methods.
These methods are similar to the UIView versions except that instead of converting to and from a view’s local
coordinate system, these methods convert to and from the window’s coordinate system.
convertPoint:fromWindow:
convertRect:fromWindow:
convertPoint:toWindow:
convertRect:toWindow:
When converting coordinates in rotated views, UIKit converts rectangles under the assumption that you want
the returned rectangle to reflect the screen area covered by the source rectangle. Figure 3-3 shows an example
of how rotations can cause the size of the rectangle to change during a conversion. In the figure, an outer
Views
Creating and Managing a View Hierarchy
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
50parent view contains a rotated subview. Converting a rectangle in the subview’s coordinate system to the
parent’s coordinate system yields a rectangle that is physically larger. This larger rectangle is actually the
smallest rectangle in the bounds of outerView that completely encloses the rotated rectangle.
Figure 3-3 Converting values in a rotated view
Rectangle in
rotatedView
coordinate system
Rectangle converted to
outerView
coordinate system
outerView
superview
subviews
frame
rotatedView
superview
subviews
frame
Adjusting the Size and Position of Views at Runtime
Whenever the size of a view changes, the size and position of its subviews must change accordingly. The
UIView class supports both the automatic and manual layout of views in a view hierarchy. With automatic
layout, you set the rules that each view should follow when its parent view resizes, and then forget about
resizing operations altogether. With manual layout, you manually adjust the size and position of views as
needed.
Being Prepared for Layout Changes
Layout changes can occur whenever any of the following events happens in a view:
● The size of a view’s bounds rectangle changes.
● An interface orientation change occurs, which usually triggers a change in the root view’s boundsrectangle.
● The set of Core Animation sublayers associated with the view’s layer changes and requires layout.
● Your application forces layout to occur by calling the setNeedsLayout or layoutIfNeeded method of
a view.
● Your application forces layout by calling the setNeedsLayout method of the view’s underlying layer
object.
Views
Adjusting the Size and Position of Views at Runtime
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
51Handling Layout Changes Automatically Using Autoresizing Rules
When you change the size of a view, the position and size of any embedded subviews usually needs to change
to account for the new size of their parent. The autoresizesSubviews property of the superview determines
whether the subviewsresize at all. If this property isset to YES, the view usesthe autoresizingMask property
of each subview to determine how to size and position that subview. Size changes to any subviews trigger
similar layout adjustments for their embedded subviews.
For each view in your view hierarchy,setting that view’s autoresizingMask property to an appropriate value
is an important part of handling automatic layout changes. Table 3-2 lists the autoresizing options you can
apply to a given view and describes their effects during layout operations. You can combine constants using
an OR operator or just add them together before assigning them to the autoresizingMask property. If you
are using Interface Builder to assemble your views, you use the Autosizing inspector to set these properties.
Table 3-2 Autoresizing mask constants
Autoresizing mask Description
UIViewAutoresizingNone The view does not autoresize. (This is the default value.)
The view’s height changes when the superview’s height changes.
If this constant is not included, the view’s height does not change.
UIViewAutoresizingFlexibleHeight
The view’s width changes when the superview's width changes. If
this constant is not included, the view’s width does not change.
UIViewAutoresizingFlexibleWidth
The distance between the view’s left edge and the superview’s left
edge grows or shrinks as needed. If this constant is not included,
the view’s left edge remains a fixed distance from the left edge of
the superview.
UIViewAutoresizingFlexibleLeftMargin
The distance between the view’s right edge and the superview’s
right edge grows or shrinks as needed. If this constant is not
included, the view’s right edge remains a fixed distance from the
right edge of the superview.
UIViewAutoresizingFlexibleRightMargin
The distance between the view’s bottom edge and the superview’s
bottom edge grows or shrinks as needed. If this constant is not
included, the view’s bottom edge remains a fixed distance from the
bottom edge of the superview.
UIViewAutoresizingFlexibleBottomMargin
The distance between the view’s top edge and the superview’s top
edge grows or shrinks as needed. If this constant is not included,
the view’s top edge remains a fixed distance from the top edge of
the superview.
UIViewAutoresizingFlexibleTopMargin
Views
Adjusting the Size and Position of Views at Runtime
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
52Figure 3-4 shows a graphical representation of how the options in the autoresizing mask apply to a view. The
presence of a given constant indicates that the specified aspect of the view is flexible and may change when
the superview’s bounds change. The absence of a constant indicates that the view’s layout is fixed in that
aspect. When you configure a view that has more than one flexible attribute along a single axis, UIKit distributes
any size changes evenly among the corresponding spaces.
Figure 3-4 View autoresizing mask constants
UIViewAutoresizingFlexibleWidth
UIViewAutoresizingFlexibleRightMargin
UIViewAutoresizingFlexibleBottomMargin
UIViewAutoresizingFlexibleHeight
Superview
View
UIViewAutoresizingFlexibleTopMargin
UIViewAutoresizingFlexibleLeftMargin
The easiest way to configure autoresizing rulesis using the Autosizing controlsin the Size inspector of Interface
Builder. The flexible width and height constants from the preceding figure have the same behavior as the
width and size indicatorsin the Autosizing controls diagram. However, the behavior and use of margin indicators
is effectively reversed. In Interface Builder, the presence of a margin indicator means that the margin has a
fixed size and the absence of the indicator means the margin has a flexible size. Fortunately, Interface Builder
provides an animation to show you how changes to the autoresizing behaviors affect your view.
Important: If a view’s transform property does not contain the identity transform, the frame of that view
is undefined and so are the results of its autoresizing behaviors.
After the automatic autoresizing rules for all affected views have been applied, UIKit goes back and gives each
view a chance to make any necessary manual adjustments to its superview. For more information about how
to manage the layout of views manually, see “Tweaking the Layout of Your Views Manually” (page 54).
Views
Adjusting the Size and Position of Views at Runtime
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
53Tweaking the Layout of Your Views Manually
Whenever the size of a view changes, UIKit appliesthe autoresizing behaviors of that view’ssubviews and then
calls the layoutSubviews method of the view to let it make manual changes. You can implement the
layoutSubviews method in custom views when the autoresizing behaviors by themselves do not yield the
results you want. Your implementation of this method can do any of the following:
● Adjust the size and position of any immediate subviews.
● Add or remove subviews or Core Animation layers.
● Force a subview to be redrawn by calling its setNeedsDisplay or setNeedsDisplayInRect: method.
One place where applications often lay out subviews manually is when implementing a large scrollable area.
Because it is impractical to have a single large view for its scrollable content, applications often implement a
root view that contains a number of smaller tile views. Each tile represents a portion of the scrollable content.
When a scroll event happens, the root view calls its setNeedsLayout method to initiate a layout change. Its
layoutSubviews method then repositions the tile views based on the amount of scrolling that occurred. As
tiles scroll out of the view’s visible area, the layoutSubviews method moves the tiles to the incoming edge,
replacing their contents in the process.
When writing your layout code, be sure to test your code in the following ways:
● Change the orientation of your views to make sure the layout looks correct in all supported interface
orientations.
● Make sure your code responds appropriately to changes in the height of the status bar. When a phone
call is active, the status bar height increasesin size, and when the user endsthe call, the status bar decreases
in size.
For information about how autoresizing behaviors affect the size and position of your views, see “Handling
Layout Changes Automatically Using Autoresizing Rules” (page 52). For an example of how to implement
tiling, see the ScrollViewSuite sample.
Modifying Views at Runtime
As applications receive input from the user, they adjust their user interface in response to that input. An
application might modify its views by rearranging them, changing their size or position, hiding or showing
them, or loading an entirely new set of views. In iOS applications, there are several places and ways in which
you perform these kinds of actions:
●
In a view controller:
Views
Modifying Views at Runtime
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
54● A view controller has to create its views before showing them. It can load the views from a nib file or
create them programmatically. When those views are no longer needed, it disposes of them.
● When a device changes orientations, a view controller might adjust the size and position of views to
match. As part of its adjustment to the new orientation, it might hide some views and show others.
● When a view controller manages editable content, it might adjust its view hierarchy when moving to
and from edit mode. For example, it might add extra buttons and other controls to facilitate editing
various aspects of its content. This might also require the resizing of any existing viewsto accommodate
the extra controls.
●
In animation blocks:
● When you want to transition between different sets of views in your user interface, you hide some
views and show others from inside an animation block.
● When implementing special effects, you might use an animation block to modify various properties
of the view. For example, to animate changes to the size of a view, you would change the size of its
frame rectangle.
● Other ways:
● When touch events or gestures occur, your interface might respond by loading a new set of views or
changing the current set of views. For information about handling events, see Event Handling Guide
for iOS .
● When the user interacts with a scroll view, a large scrollable area might hide and show tile subviews.
For more information about supporting scrollable content, see Scroll View Programming Guide for
iOS .
● When the keyboard appears, you might reposition or resize views so that they do not lie underneath
the keyboard. For information about how to interact with the keyboard, see Text, Web, and Editing
Programming Guide for iOS .
View controllers are a common place to initiate changes to your views. Because a view controller manages the
view hierarchy associated with the content being displayed, it is ultimately responsible for everything that
happens to those views. When loading its views or handling orientation changes, the view controller can add
new views, hide or replace existing ones, and make any number of changes to make the views ready for the
display. And if you implement support for editing your view’s content, the setEditing:animated: method
in UIViewController gives you a place to transition your views to and from their editable versions.
Animation blocks are another common place to initiate view-related changes. The animation support built
into the UIView class makes it easy to animate changes to view properties. You can also use the
transitionWithView:duration:options:animations:completion: or
transitionFromView:toView:duration:options:completion: methods to swap out entire sets of
views for new ones.
Views
Modifying Views at Runtime
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
55For more information about animating views and initiating view transitions, see “Animations” (page 64). For
more information on how you use view controllers to manage view-related behaviors, see View Controller
Programming Guide for iOS .
Interacting with Core Animation Layers
Each view object has a dedicated Core Animation layer that manages the presentation and animation of the
view’s content on the screen. Although you can do a lot with your view objects, you can also work directly
with the corresponding layer objects as needed. The layer object for the view is stored in the view’s layer
property.
Changing the Layer Class Associated with a View
The type of layer associated with a view cannot be changed after the view is created. Therefore, each view
uses the layerClass class method to specify the class of its layer object. The default implementation of this
method returns the CALayer class and the only way to change this value is to subclass, override the method,
and return a different value. You might want to change this value in the following circumstances:
● Your application uses OpenGL ES for drawing, in which case, the layer must be an instance of the
CAEAGLLayer class.
● Your view uses tiling to display a large scrollable area, in which case you might want to use the
CATiledLayer class to back your view instead.
Implementation of the layerClass method should simply create the desired Class object and return it. For
example, a view that supports OpenGL ES drawing would have the following implementation for this method:
+ (Class)layerClass
{
return [CAEAGLLayer class];
}
Each view calls its layerClass method early in its initialization process and uses the returned class to create
its layer object. In addition, the view always assigns itself as the delegate of its layer object. At this point, the
view ownsitslayer and the relationship between the view and layer must not change. You must also not assign
the same view as the delegate of any other layer object. Changing the ownership or delegate relationships of
the view will cause drawing problems and potential crashes in your application.
Views
Interacting with Core Animation Layers
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
56For more information about the different types of layer objects provided by Core Animation,see Core Animation
Reference Collection .
Embedding Layer Objects in a View
If you prefer to work primarily with layer objects instead of views, you can incorporate custom layer objects
into your view hierarchy as needed. A custom layer object is any instance of CALayer that is not owned by a
view. You typically create custom layers programmatically and incorporate them using Core Animation routines.
Custom layers do not receive events or participate in the responder chain but do draw themselves and respond
to size changes in their parent view or layer according to the Core Animation rules.
Listing 3-3 shows an example of the viewDidLoad method from a view controller that creates a custom layer
object and addsit to itsroot view. The layer is used to display a static image that is animated. Instead of adding
the layer to the view itself, you add it to the view’s underlying layer.
Listing 3-3 Adding a custom layer to a view
- (void)viewDidLoad {
[super viewDidLoad];
// Create the layer.
CALayer* myLayer = [[CALayer alloc] init];
// Set the contents of the layer to a fixed image. And set
// the size of the layer to match the image size.
UIImage layerContents = [[UIImage imageNamed:@"myImage"] retain];
CGSize imageSize = layerContents.size;
myLayer.bounds = CGRectMake(0, 0, imageSize.width, imageSize.height);
myLayer = layerContents.CGImage;
// Add the layer to the view.
CALayer* viewLayer = self.view.layer;
[viewLayer addSublayer:myLayer];
// Center the layer in the view.
CGRect viewBounds = backingView.bounds;
Views
Interacting with Core Animation Layers
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
57myLayer.position = CGPointMake(CGRectGetMidX(viewBounds),
CGRectGetMidY(viewBounds));
// Release the layer, since it is retained by the view's layer
[myLayer release];
}
You can add any number of sublayers and arrange them into sublayer hierarchies, if you want. However, at
some point, those layers must be attached to the layer object of a view.
For information on how to work with layers directly, see Core Animation Programming Guide .
Defining a Custom View
If the standard system views do not do exactly what you need, you can define a custom view. Custom views
give you total control over the appearance of your application’s content and how interactions with that content
are handled.
Checklist for Implementing a Custom View
The job of a custom view is to present content and manage interactions with that content. The successful
implementation of a custom view involves more than just drawing and handling events, though. The following
checklist includes the more important methods you can override (and behaviors you can provide) when
implementing a custom view:
● Define the appropriate initialization methods for your view:
● For views you plan to create programmatically, override the initWithFrame: method or define a
custom initialization method.
● For views you plan to load from nib files, override the initWithCoder: method. Use this method
to initialize your view and put it into a known state.
●
Implement a dealloc method to handle the cleanup of any custom data.
● To handle any custom drawing, override the drawRect: method and do your drawing there.
● Set the autoresizingMask property of the view to define its autoresizing behavior.
●
If your view class manages one or more integral subviews, do the following:
● Create those subviews during your view’s initialization sequence.
● Set the autoresizingMask property of each subview at creation time.
Views
Defining a Custom View
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
58●
If your subviews require custom layout, override the layoutSubviews method and implement your
layout code there.
● To handle touch-based events, do the following:
● Attach any suitable gesture recognizersto the view by using the addGestureRecognizer: method.
● For situations where you want to process the touches yourself, override the
touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, and
touchesCancelled:withEvent: methods. (Remember that you should always override the
touchesCancelled:withEvent: method, regardless of which other touch-related methods you
override.)
●
If you want the printed version of your view to look different from the onscreen version, implement the
drawRect:forViewPrintFormatter: method. For detailed information about how to support printing
in your views, see Drawing and Printing Guide for iOS .
In addition to overriding methods, remember that there is a lot you can do with the view’s existing properties
and methods. For example, the contentMode and contentStretch properties let you change the final
rendered appearance of your view and might be preferable to redrawing the content yourself. In addition to
the UIView class itself, there are many aspects of a view’s underlying CALayer object that you can configure
directly or indirectly. You can even change the class of the layer object itself (which you must do if you plan
to use OpenGL ES to draw your view’s content).
For more information about the methods and properties of the view class, see UIView Class Reference .
Initializing Your Custom View
Every new view object you define should include a custom initWithFrame: initializer method. This method
is responsible for initializing the class at creation time and putting your view object into a known state. You
use this method when creating instances of your view programmatically in your code.
Listing 3-4 shows a skeletal implementation of a standard initWithFrame: method. This method calls the
inherited implementation of the method first and then initializes the instance variables and state information
of the class before returning the initialized object. Calling the inherited implementation istraditionally performed
first so that if there is a problem, you can abort your own initialization code and return nil.
Listing 3-4 Initializing a view subclass
- (id)initWithFrame:(CGRect)aRect {
self = [super initWithFrame:aRect];
if (self) {
// setup the initial properties of the view
Views
Defining a Custom View
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
59...
}
return self;
}
If you plan to load instances of your custom view class from a nib file, you should be aware that in iOS, the
nib-loading code does not use the initWithFrame: method to instantiate new view objects. Instead, it uses
the initWithCoder: method that is part of the NSCoding protocol.
Even if your view adopts the NSCoding protocol, Interface Builder does not know about your view’s custom
properties and therefore does not encode those properties into the nib file. As a result, your own
initWithCoder: method should perform whatever initialization code it can to put the view into a known
state. You can also implement the awakeFromNib method in your view class and use that method to perform
additional initialization.
Implementing Your Drawing Code
For viewsthat need to do custom drawing, you need to override the drawRect: method and do your drawing
there. Custom drawing is recommended only as a last resort. In general, if you can use other views to present
your content, that is preferred.
The implementation of your drawRect: method should do exactly one thing: draw your content. This method
is not the place to be updating your application’s data structures or performing any tasks not related to drawing.
It should configure the drawing environment, draw your content, and exit as quickly as possible. And if your
drawRect: method might be called frequently, you should do everything you can to optimize your drawing
code and draw as little as possible each time the method is called.
Before calling your view’s drawRect: method, UIKit configures the basic drawing environment for your view.
Specifically, it creates a graphics context and adjusts the coordinate system and clipping region to match the
coordinate system and visible bounds of your view. Thus, by the time your drawRect: method is called, you
can begin drawing your content using native drawing technologies such as UIKit and Core Graphics. You can
get a pointer to the current graphics context using the UIGraphicsGetCurrentContext function.
Views
Defining a Custom View
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
60Important: The current graphics context is valid only for the duration of one call to your view’s drawRect:
method. UIKit might create a different graphics context for each subsequent call to this method, so you
should not try to cache the object and use it later.
Listing 3-5 shows a simple implementation of a drawRect: method that draws a 10-pixel-wide red border
around the view. Because UIKit drawing operations use Core Graphics for their underlying implementations,
you can mix drawing calls, as shown here, to get the results you expect.
Listing 3-5 A drawing method
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect myFrame = self.bounds;
// Set the line width to 10 and inset the rectangle by
// 5 pixels on all sides to compensate for the wider line.
CGContextSetLineWidth(context, 10);
CGRectInset(myFrame, 5, 5);
[[UIColor redColor] set];
UIRectFrame(myFrame);
}
If you know that your view’s drawing code always covers the entire surface of the view with opaque content,
you can improve system performance by setting the opaque property of your view to YES. When you mark a
view as opaque, UIKit avoids drawing content that is located immediately behind your view. This not only
reduces the amount of time spent drawing but also minimizes the work that must be done to composite your
view with other content. However, you should set this property to YES only if you know your view’s content
is completely opaque. If your view cannot guarantee that its contents are always opaque, you should set the
property to NO.
Another way to improve drawing performance, especially during scrolling, is to set the
clearsContextBeforeDrawing property of your view to NO. When this property is set to YES, UIKIt
automatically fills the area to be updated by your drawRect: method with transparent black before calling
your method. Setting this property to NO eliminates the overhead for that fill operation but puts the burden
on your application to fill the update rectangle passed to your drawRect: method with content.
Views
Defining a Custom View
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
61Responding to Events
View objects are responder objects—instances of the UIResponder class—and are therefore capable of receiving
touch events. When a touch event occurs, the window dispatches the corresponding event object to the view
in which the touch occurred. If your view is not interested in an event, it can ignore it or passit up the responder
chain to be handled by a different object.
In addition to handling touch events directly, views can also use gesture recognizers to detect taps, swipes,
pinches, and other types of common touch-related gestures. Gesture recognizers do the hard work of tracking
touch events and making sure that they follow the right criteria to qualify them as the target gesture. Instead
of your application having to track touch events, you can create the gesture recognizer, assign an appropriate
target object and action method to it, and install it on your view using the addGestureRecognizer: method.
The gesture recognizer then calls your action method when the corresponding gesture occurs.
If you prefer to handle touch events directly, you can implement the following methods for your view, which
are described in more detail in Event Handling Guide for iOS :
touchesBegan:withEvent:
touchesMoved:withEvent:
touchesEnded:withEvent:
touchesCancelled:withEvent:
The default behavior for views is to respond to only one touch at a time. If the user puts a second finger down,
the system ignoresthe touch event and does not report it to your view. If you plan to track multifinger gestures
from your view’s event-handler methods, you need to enable multitouch events by setting the
multipleTouchEnabled property of your view to YES.
Some views, such as labels and images, disable event handling altogether initially. You can control whether a
view is able to receive touch events by changing the value of the view’s userInteractionEnabled property.
You might temporarily set this property to NO to prevent the user from manipulating the contents of your view
while a long operation is pending. To prevent events from reaching any of your views, you can also use the
beginIgnoringInteractionEvents and endIgnoringInteractionEvents methods of the
UIApplication object. These methods affect the delivery of events for the entire application, not just for a
single view.
Views
Defining a Custom View
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
62Note: The animation methods of UIView typically disable touch events while animations are in
progress. You can override this behavior by configuring the animation appropriately. For more
information about performing animations, see “Animations” (page 64).
As it handles touch events, UIKit uses the hitTest:withEvent: and pointInside:withEvent: methods
of UIView to determine whether a touch event occurred inside a given view’s bounds. Although you rarely
need to override these methods, you could do so to implement custom touch behaviors for your view. For
example, you could override these methods to prevent subviews from handling touch events.
Cleaning Up After Your View
If your view class allocates any memory, stores references to any custom objects, or holds resources that must
be released when the view is released, you must implement a dealloc method. The system calls the dealloc
method when your view’s retain count reaches zero and it is time to deallocate the view. Your implementation
of this method should release any objects or resources held by the view and then call the inherited
implementation, as shown in Listing 3-6. You should not use this method to perform any other types of tasks.
Listing 3-6 Implementing the dealloc method
- (void)dealloc {
// Release a retained UIColor object
[color release];
// Call the inherited implementation
[super dealloc];
}
Views
Defining a Custom View
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
63Animations provide fluid visual transitions between different states of your user interface. In iOS, animations
are used extensively to reposition views, change theirsize, remove them from view hierarchies, and hide them.
You might use animations to convey feedback to the user or to implement interesting visual effects.
In iOS, creating sophisticated animations does not require you to write any drawing code. All of the animation
techniques described in this chapter use the built-in support provided by Core Animation. All you have to do
is trigger the animation and let Core Animation handle the rendering of individual frames. This makes creating
sophisticated animations very easy with only a few lines of code.
What Can Be Animated?
Both UIKit and Core Animation provide support for animations, but the level of support provided by each
technology varies. In UIKit, animations are performed using UIView objects. Views support a basic set of
animations that cover many common tasks. For example, you can animate changes to properties of views or
use transition animations to replace one set of views with another.
Table 4-1 liststhe animatable properties—the propertiesthat have built-in animation support—of the UIView
class. Being animatable does not mean animations happen automatically. Changing the value of these properties
normally just updates the property (and the view) immediately without an animation. To animate such a
change, you must change the property’s value from inside an animation block, which is described in “Animating
Property Changes in a View” (page 66).
Table 4-1 Animatable UIView properties
Property Changes you can make
Modify this property to change the view’s size and position relative to its
superview’s coordinate system. (If the transform property does not contain
the identity transform, modify the bounds or center properties instead.)
frame
bounds Modify this property to change the view’s size.
Modify this property to change the view’s position relative to its superview’s
coordinate system.
center
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
64
AnimationsProperty Changes you can make
Modify this property to scale, rotate, or translate the view relative to its center
point. Transformations using this property are always performed in 2D space.
(To perform 3D transformations, you must animate the view’slayer object using
Core Animation.)
transform
alpha Modify this property to gradually change the transparency of the view.
backgroundColor Modify this property to change the view’s background color.
Modify this property to change the way the view’s contents are stretched to
fill the available space.
contentStretch
Animated view transitions are a way for you to make changes to your view hierarchy beyond those offered by
view controllers. Although you should use view controllers to manage succinct view hierarchies, there may be
times when you want to replace all or part of a view hierarchy. In those situations, you can use view-based
transitions to animate the addition and removal of your views.
In places where you want to perform more sophisticated animations, or animations not supported by the
UIView class, you can use Core Animation and the view’s underlying layer to create the animation. Because
view and layer objects are intricately linked together, changes to a view’s layer affect the view itself. Using
Core Animation, you can animate the following types of changes for your view’s layer:
● The size and position of the layer
● The center point used when performing transformations
● Transformations to the layer or its sublayers in 3D space
● The addition or removal of a layer from the layer hierarchy
● The layer’s Z-order relative to other sibling layers
● The layer’s shadow
● The layer’s border (including whether the layer’s corners are rounded)
● The portion of the layer that stretches during resizing operations
● The layer’s opacity
● The clipping behavior for sublayers that lie outside the layer’s bounds
● The current contents of the layer
● The rasterization behavior of the layer
Animations
What Can Be Animated?
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
65Note: If your view hosts custom layer objects—that is, layer objects without an associated view—you
must use Core Animation to animate any changes to them.
Although this chapter addresses a few Core Animation behaviors, it does so in relation to initiating them from
your view code. For more complete information about how to use Core Animation to animate layers, see Core
Animation Programming Guide and Core Animation Cookbook .
Animating Property Changes in a View
In order to animate changesto a property of the UIView class, you must wrap those changesinside an animation
block. The term animation block is used in the generic sense to refer to any code that designates animatable
changes. In iOS 4 and later, you create an animation block using block objects. In earlier versions of iOS, you
mark the beginning and end of an animation block using special class methods of the UIView class. Both
techniques support the same configuration options and offer the same amount of control over the animation
execution. However, the block-based methods are preferred whenever possible.
The following sections focus on the code you need in order to animate changes to view properties. For
information about how to create animated transitions between sets of views,see “Creating Animated Transitions
Between Views” (page 73).
Starting Animations Using the Block-Based Methods
In iOS 4 and later, you use the block-based class methods to initiate animations. There are several block-based
methods that offer different levels of configuration for the animation block. These methods are:
● animateWithDuration:animations:
● animateWithDuration:animations:completion:
● animateWithDuration:delay:options:animations:completion:
Because these are class methods, the animation blocks you create with them are not tied to a single view. Thus,
you can use these methods to create a single animation that involves changes to multiple views. For example,
Listing 4-1 showsthe code needed to fade in one view while fading out another over a one second time period.
When this code executes, the specified animations are started immediately on another thread so as to avoid
blocking the current thread or your application’s main thread.
Listing 4-1 Performing a simple block-based animation
[UIView animateWithDuration:1.0 animations:^{
Animations
Animating Property Changes in a View
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
66firstView.alpha = 0.0;
secondView.alpha = 1.0;
}];
The animations in the preceding example run only once using using an ease-in, ease-out animation curve. If
you want to change the default animation parameters, you must use the
animateWithDuration:delay:options:animations:completion:method to performyour animations.
This method lets you customize the following animation parameters:
● The delay to use before starting the animation
● The type of timing curve to use during the animation
● The number of times the animation should repeat
● Whether the animation should reverse itself automatically when it reaches the end
● Whether touch events are delivered to views while the animations are in progress
● Whether the animation should interrupt any in-progress animations or wait until those are complete before
starting
Another thing that both the animateWithDuration:animations:completion: and
animateWithDuration:delay:options:animations:completion: methods support is the ability to
specify a completion handler block. You might use a completion handler to signal your application that a
specific animation has finished. Completion handlers are also the way to link separate animations together.
Listing 4-2 shows an example of an animation block that uses a completion handler to initiate a new animation
after the first one finishes. The first call to
animateWithDuration:delay:options:animations:completion: sets up a fade-out animation and
configures it with some custom options. When that animation is complete, its completion handler runs and
sets up the second half of the animation, which fades the view back in after a delay.
Using a completion handler is the primary way that you link multiple animations.
Listing 4-2 Creating an animation block with custom options
- (IBAction)showHideView:(id)sender
{
// Fade out the view right away
[UIView animateWithDuration:1.0
delay: 0.0
options: UIViewAnimationOptionCurveEaseIn
Animations
Animating Property Changes in a View
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
67animations:^{
thirdView.alpha = 0.0;
}
completion:^(BOOL finished){
// Wait one second and then fade in the view
[UIView animateWithDuration:1.0
delay: 1.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
thirdView.alpha = 1.0;
}
completion:nil];
}];
}
Important: Changing the value of a property while an animation involving that property is already in
progress does not stop the current animation. Instead, the current animation continues and animates to
the new value you just assigned to the property.
Starting Animations Using the Begin/Commit Methods
If your application runs in iOS 3.2 and earlier, you must use the beginAnimations:context: and
commitAnimations class methods of UIView to define your animation blocks. These methods mark the
beginning and end of your animation block. Any animatable properties you change between these methods
are animated to their new values after you call the commitAnimations method. Execution of the animations
occurs on a secondary thread so as to avoid blocking the current thread or your application’s main thread.
Note: If you are writing an application for iOS 4 or later, you should use the block-based methods
for animating your content instead. For information on how to use those methods, see “Starting
Animations Using the Block-Based Methods” (page 66).
Listing 4-3 shows the code needed to implement the same behavior as Listing 4-1 (page 66) but using the
begin/commit methods. Asin Listing 4-1, this code fades one view out while fading another in over one second
of time. However, in this example, you must set the duration of the animation using a separate method call.
Animations
Animating Property Changes in a View
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
68Listing 4-3 Performing a simple begin/commit animation
[UIView beginAnimations:@"ToggleViews" context:nil];
[UIView setAnimationDuration:1.0];
// Make the animatable changes.
firstView.alpha = 0.0;
secondView.alpha = 1.0;
// Commit the changes and perform the animation.
[UIView commitAnimations];
By default, all animatable property changes within an animation block are animated. If you want to animate
some changes but not others, use the setAnimationsEnabled: method to disable animations temporarily,
make any changesthat you do not want animated, and then call setAnimationsEnabled: again to reenable
animations. You can determine if animations are current enabled by calling the areAnimationsEnabled
class method.
Note: Changing the value of a property while an animation involving that property is in progress
does not stop the current animation. Instead, the animation continues and animates to the new
value you just assigned to the property.
Configuring the Parameters for Begin/Commit Animations
To configure the animation parameters for a begin/commit animation block, you use any of several UIView
class methods. Table 4-2 lists these methods and describes how you use them to configure your animations.
Most of these methods should be called only from inside a begin/commit animation block but some may also
be used with block-based animations. If you do not call one of these methods from your animation block, a
default value for the corresponding attribute is used. For more information about the default value associated
with each method, see the method description in UIView Class Reference .
Table 4-2 Methods for configuring animation blocks
Method Usage
Use either of these methodsto specify when the executionsshould
begin executing. If the specified start date is in the past (or the
delay is 0), the animations begin as soon as possible.
setAnimationStartDate:
setAnimationDelay:
Use this method to set the period of time over which to execute
the animations.
setAnimationDuration:
Animations
Animating Property Changes in a View
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
69Method Usage
Use this method to set the timing curve of the animations. This
controls whether animations execute linearly or change speed at
certain times.
setAnimationCurve:
Use these methods to set the number of times the animation
repeats and whether the animation runs in reverse at the end of
each complete cycle. For more information about using these
methods, see “Implementing Animations That Reverse
Themselves” (page 73).
setAnimationRepeatCount:
setAnimationRepeatAutoreverses:
Use these methods to execute code immediately before or after
the animations. For more information about using a delegate,see
“Configuring an Animation Delegate” (page 71).
setAnimationDelegate:
setAnimationWillStartSelector:
setAnimationDidStopSelector:
Use this method to stop all previous animations immediately and
start the new animations from the stopping point. If you pass NO
to this method, instead of YES, the new animations do not begin
executing until the previous animations stop.
setAnimationBeginsFromCurrentState:
Listing 4-4 shows the code needed to implement the same behavior as the code in Listing 4-2 (page 67) but
using the begin/commit methods. As before, this code fades out a view, waits one second, and then fades it
back in. In order to implement the second part of the animation, the code sets up an animation delegate and
implements a did-stop handler method. That handler method then sets up the second half of the animations
and runs them.
Listing 4-4 Configuring animation parameters using the begin/commit methods
// This method begins the first animation.
- (IBAction)showHideView:(id)sender
{
[UIView beginAnimations:@"ShowHideView" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDuration:1.0];
[UIView setAnimationDelegate:self];
[UIView
setAnimationDidStopSelector:@selector(showHideDidStop:finished:context:)];
Animations
Animating Property Changes in a View
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
70// Make the animatable changes.
thirdView.alpha = 0.0;
// Commit the changes and perform the animation.
[UIView commitAnimations];
}
// Called at the end of the preceding animation.
- (void)showHideDidStop:(NSString *)animationID finished:(NSNumber *)finished
context:(void *)context
{
[UIView beginAnimations:@"ShowHideView2" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
[UIView setAnimationDuration:1.0];
[UIView setAnimationDelay:1.0];
thirdView.alpha = 1.0;
[UIView commitAnimations];
}
Configuring an Animation Delegate
If you want to execute code immediately before or after an animation, you must associate a delegate object
and a start or stop selector with your begin/commit animation block. You set your delegate object using the
setAnimationDelegate: class method of UIView and you set your start and stop selectors using the
setAnimationWillStartSelector: and setAnimationDidStopSelector: class methods. During the
animation, the animation system calls your delegate methods at the appropriate times to give you a chance
to perform your code.
The signatures of your animation delegate methods need to be similar to the following:
- (void)animationWillStart:(NSString *)animationID context:(void *)context;
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished
context:(void *)context;
The animationID and context parameters for both methods are the same parameters that you passed to
the beginAnimations:context: method at the beginning of the animation block:
Animations
Animating Property Changes in a View
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
71● animationID—An application-supplied string used to identify the animation.
● context—An application-supplied object that you can use to pass additional information to the delegate.
The setAnimationDidStopSelector: selector method has an additional parameter—a Boolean value that
is YES if the animation ran to completion. If the value of this parameter is NO, the animation was either canceled
or stopped prematurely by another animation.
Note: Although animation delegates can be used in the block-based methods, there is generally
no need to use them there. Instead, place any code you want to run before the animations at the
beginning of your block and place any code you want to run after the animationsfinish in a completion
handler.
Nesting Animation Blocks
You can assign different timing and configuration options to parts of an animation block by nesting additional
animation blocks. As the name implies, a nested animation block is a new animation block created inside an
existing animation block. Nested animations are started at the same time as any parent animations but run
(for the most part) with their own configuration options. By default, nested animations do inherit the parent’s
duration and animation curve but even those options can be overridden as needed.
Listing 4-5 shows an example of how a nested animation is used to change the timing, duration, and behavior
of some animations in the overall group. In this case, two views are being faded to total transparency, but the
transparency of the anotherView object is changed back and forth several times before it is finally hidden.
The UIViewAnimationOptionOverrideInheritedCurve and
UIViewAnimationOptionOverrideInheritedDuration keys used in the nested animation block allow
the curve and duration values from the first animation to be modified for the second animation. If these keys
were not present, the duration and curve of the outer animation block would be used instead.
Listing 4-5 Nesting animations that have different configurations
[UIView animateWithDuration:1.0
delay: 1.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
aView.alpha = 0.0;
// Create a nested animation that has a different
// duration, timing curve, and configuration.
Animations
Animating Property Changes in a View
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
72[UIView animateWithDuration:0.2
delay:0.0
options: UIViewAnimationOptionOverrideInheritedCurve |
UIViewAnimationOptionCurveLinear |
UIViewAnimationOptionOverrideInheritedDuration |
UIViewAnimationOptionRepeat |
UIViewAnimationOptionAutoreverse
animations:^{
[UIView setAnimationRepeatCount:2.5];
anotherView.alpha = 0.0;
}
completion:nil];
}
completion:nil];
If you are using the begin/commit methods to create your animations, nesting works in much the same way
as with the block-based methods. Each successive call to beginAnimations:context: within an already
open animation block creates a new nested animation block that you can configure as needed. Any configuration
changes you make apply to the most recently opened animation block. All animation blocks must be closed
with a call to commitAnimations before the animations are submitted and executed.
Implementing Animations That Reverse Themselves
When creating reversible animations in conjunction with a repeat count, consider specifying a non integer
value for the repeat count. For an autoreversing animation, each complete cycle of the animation involves
animating from the original value to the new value and back again. If you want your animation to end on the
new value, adding 0.5 to the repeat count causes the animation to complete the extra half cycle needed to
end at the new value. If you do not include this half step, your animation will animate to the original value and
then snap quickly to the new value, which may not be the visual effect you want.
Creating Animated Transitions Between Views
View transitions help you hide sudden changes associated with adding, removing, hiding, or showing views
in your view hierarchy. You use view transitions to implement the following types of changes:
Animations
Creating Animated Transitions Between Views
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
73● Change the visible subviews of an existing view. You typically choose this option when you want to
make relatively small changes to an existing view.
● Replace one view in your view hierarchy with a different view. You typically choose this option when
you want to replace a view hierarchy that spans all or most of the screen.
Important: View transitions should not be confused with transitions initiated by view controllers, such as
the presentation of modal view controllers or the pushing of new view controllers onto a navigation stack.
View transitions affect the view hierarchy only, whereas view-controller transitions change the active view
controller as well. Thus, for view transitions, the view controller that was active when you initiated the
transition remains active when the transition finishes.
For more information about how you can use view controllers to present new content, see View Controller
Programming Guide for iOS .
Changing the Subviews of a View
Changing the subviews of a view allows you to make moderate changes to the view. For example, you might
add or remove subviews to toggle the superview between two different states. By the time the animations
finish, the same view is displayed but its contents are now different.
In iOS 4 and later, you use the transitionWithView:duration:options:animations:completion:
method to initiate a transition animation for a view. In the animations block passed to this method, the only
changesthat are normally animated are those associated with showing, hiding, adding, or removing subviews.
Limiting animations to this set allows the view to create a snapshot image of the before and after versions of
the view and animate between the two images, which is more efficient. However, if you need to animate other
changes, you can include the UIViewAnimationOptionAllowAnimatedContent option when calling the
method. Including that option prevents the view from creating snapshots and animates all changes directly.
Listing 4-6 is an example of how to use a transition animation to make it seem as if a new text entry page has
been added. In this example, the main view contains two embedded text views. The text views are configured
identically, but one is always visible while the other is always hidden. When the user taps the button to create
a new page, this method toggles the visibility of the two views, resulting in a new empty page with an empty
text view ready to accept text. After the transition is complete, the view saves the text from the old page using
a private method and resets the now hidden text view so that it can be reused later. The view then arranges
its pointers so that it can be ready to do the same thing if the user requests yet another new page.
Listing 4-6 Swapping an empty text view for an existing one
- (IBAction)displayNewPage:(id)sender
{
Animations
Creating Animated Transitions Between Views
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
74[UIView transitionWithView:self.view
duration:1.0
options:UIViewAnimationOptionTransitionCurlUp
animations:^{
currentTextView.hidden = YES;
swapTextView.hidden = NO;
}
completion:^(BOOL finished){
// Save the old text and then swap the views.
[self saveNotes:temp];
UIView* temp = currentTextView;
currentTextView = swapTextView;
swapTextView = temp;
}];
}
If you need to perform view transitions in iOS 3.2 and earlier, you can use the
setAnimationTransition:forView:cache: method to specify the parameters for the transition. The
view you pass to that method is the same one you would pass in as the first parameter to the
transitionWithView:duration:options:animations:completion: method. Listing 4-7 shows the
basic structure of the animation block you need to create. Note that to implement the completion block shown
in Listing 4-6 (page 74), you would need to configure an animation delegate with a did-stop handler as
described in “Configuring an Animation Delegate” (page 71).
Listing 4-7 Changing subviews using the begin/commit methods
[UIView beginAnimations:@"ToggleSiblings" context:nil];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.view
cache:YES];
[UIView setAnimationDuration:1.0];
// Make your changes
[UIView commitAnimations];
Animations
Creating Animated Transitions Between Views
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
75Replacing a View with a Different View
Replacing views is something you do when you want your interface to be dramatically different. Because this
technique swaps only views (and not view controllers), you are responsible for designing your application’s
controller objects appropriately. This technique is simply a way of presenting new views quickly using some
standard transitions.
In iOS 4 and later, you use the transitionFromView:toView:duration:options:completion: method
to transition between two views. This method actually removes the first view from your hierarchy and inserts
the other, so you should make sure you have a reference to the first view if you want to keep it. If you want to
hide views instead of remove them from your view hierarchy, pass the
UIViewAnimationOptionShowHideTransitionViews key as one of the options.
Listing 4-8 shows the code needed to swap between two main views managed by a single view controller. In
this example, the view controller’s root view always displays one of two child views (primaryView or
secondaryView). Each view presents the same content but does so in a different way. The view controller
uses the displayingPrimary member variable (a Boolean value) to keep track of which view is displayed
at any given time. The flip direction changes depending on which view is being displayed.
Listing 4-8 Toggling between two views in a view controller
- (IBAction)toggleMainViews:(id)sender {
[UIView transitionFromView:(displayingPrimary ? primaryView : secondaryView)
toView:(displayingPrimary ? secondaryView : primaryView)
duration:1.0
options:(displayingPrimary ? UIViewAnimationOptionTransitionFlipFromRight
:
UIViewAnimationOptionTransitionFlipFromLeft)
completion:^(BOOL finished) {
if (finished) {
displayingPrimary = !displayingPrimary;
}
}];
}
Animations
Creating Animated Transitions Between Views
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
76Note: In addition to swapping out views, your view controller code needs to manage the loading
and unloading of both the primary and secondary views. For information on how views are loaded
and unloaded by a view controller, see View Controller Programming Guide for iOS .
Linking Multiple Animations Together
The UIView animation interfaces provide support for linking separate animation blocks so that they perform
sequentially instead of at the same time. The process for linking animation blocks depends on whether you
are using the block-based animation methods or the begin/commit methods:
● For block-based animations, use the completion handler supported by the
animateWithDuration:animations:completion: and
animateWithDuration:delay:options:animations:completion: methods to execute any
follow-on animations.
● For begin/commit animations, associate a delegate object and a did-stop selector with the animation. For
information about how to associate a delegate with your animations, see “Configuring an Animation
Delegate” (page 71).
An alternative to linking animations together is to use nested animations with different delay factors so as to
start the animations at different times. For more information on how to nest animations,see “Nesting Animation
Blocks” (page 72).
Animating View and Layer Changes Together
Applications can freely mix view-based and layer-based animation code as needed but the process for
configuring your animation parameters depends on who owns the layer. Changing a view-owned layer is the
same as changing the view itself, and any animations you apply to the layer’s properties respect the animation
parameters of the current view-based animation block. The same is not true for layers that you create yourself.
Custom layer objects ignore view-based animation block parameters and use the default Core Animation
parameters instead.
If you want to customize the animation parametersfor layers you create, you must use Core Animation directly.
Typically, animating layers using Core Animation involves creating a CABasicAnimation object orsome other
concrete subclass of CAAnimation. You then add that animation to the corresponding layer. You can apply
the animation from either inside or outside a view-based animation block.
Animations
Linking Multiple Animations Together
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
77Listing 4-9 shows an animation that modifies a view and a custom layer at the same time. The view in this
example contains a custom CALayer object at the center of its bounds. The animation rotatesthe view counter
clockwise while rotating the layer clockwise. Because the rotations are in opposite directions, the layer maintains
its original orientation relative to the screen and does not appear to rotate significantly. However, the view
beneath that layerspins 360 degrees and returnsto its original orientation. This example is presented primarily
to demonstrate how you can mix view and layer animations. Thistype of mixing should not be used in situations
where precise timing is needed.
Listing 4-9 Mixing view and layer animations
[UIView animateWithDuration:1.0
delay:0.0
options: UIViewAnimationOptionCurveLinear
animations:^{
// Animate the first half of the view rotation.
CGAffineTransform xform =
CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-180));
backingView.transform = xform;
// Rotate the embedded CALayer in the opposite direction.
CABasicAnimation* layerAnimation = [CABasicAnimation
animationWithKeyPath:@"transform"];
layerAnimation.duration = 2.0;
layerAnimation.beginTime = 0; //CACurrentMediaTime() + 1;
layerAnimation.valueFunction = [CAValueFunction
functionWithName:kCAValueFunctionRotateZ];
layerAnimation.timingFunction = [CAMediaTimingFunction
functionWithName:kCAMediaTimingFunctionLinear];
layerAnimation.fromValue = [NSNumber numberWithFloat:0.0];
layerAnimation.toValue = [NSNumber
numberWithFloat:DEGREES_TO_RADIANS(360.0)];
layerAnimation.byValue = [NSNumber
numberWithFloat:DEGREES_TO_RADIANS(180.0)];
[manLayer addAnimation:layerAnimation forKey:@"layerAnimation"];
}
completion:^(BOOL finished){
// Now do the second half of the view rotation.
[UIView animateWithDuration:1.0
Animations
Animating View and Layer Changes Together
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
78delay: 0.0
options: UIViewAnimationOptionCurveLinear
animations:^{
CGAffineTransform xform =
CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-359));
backingView.transform = xform;
}
completion:^(BOOL finished){
backingView.transform = CGAffineTransformIdentity;
}];
}];
Note: In Listing 4-9 (page 78), you could also create and apply the CABasicAnimation object
outside of the view-based animation block to achieve the same results. All of the animations ultimately
rely on Core Animation for their execution. Thus, if they are submitted at approximately the same
time, they run together.
If precise timing between your view and layer based animations is required, it is recommended that you create
all of the animations using Core Animation. You may find that some animations are easier to perform using
Core Animation anyway. For example, the view-based rotation in Listing 4-9 (page 78) requires a multistep
sequence for rotations of more than 180 degrees, whereas the Core Animation portion uses a rotation value
function that rotates from start to finish through a middle value.
For more information about how to create and configure animations using Core Animation,see Core Animation
Programming Guide and Core Animation Cookbook .
Animations
Animating View and Layer Changes Together
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
79This table describes the changes to View Programming Guide for iOS .
Date Notes
2011-03-08 Reorganized and expanded the content of the document.
Added information on how to create view-based animations.
Incorporated information on how to display content on an external display.
Added information about how to work with high-resolution screens.
New document describing the creation and management of views,
windows, and other visual interface elements.
2010-05-17
2011-03-08 | © 2011 Apple Inc. All Rights Reserved.
80
Document Revision HistoryApple Inc.
© 2011 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Cocoa, iPad, iPhone, iPod,
iPod touch, Quartz, and Xcode are trademarks of
Apple Inc., registered in the U.S. and other
countries.
Retina is a trademark of Apple Inc.
OpenGL is a registered trademark of Silicon
Graphics, Inc.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
Stream Programming
GuideContents
Introduction to Stream Programming Guide for Cocoa 4
Organization of This Document 4
See Also 4
Cocoa Streams 6
Reading From Input Streams 8
Preparing the Stream Object 8
Handling Stream Events 9
Disposing of the Stream Object 11
Writing To Output Streams 12
Preparing the Stream Object 12
Handling Stream Events 13
Disposing of the Stream Object 15
Polling Versus Run-Loop Scheduling 17
Handling Stream Errors 20
Setting Up Socket Streams 22
Basic Procedure 22
Securing and Configuring the Connection 23
Initiating an HTTP Request 24
For More Information 25
Document Revision History 26
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
2Figures and Listings
Cocoa Streams 6
Figure 1 Sources and destinations of stream objects 6
Reading From Input Streams 8
Listing 1 Creating and initializing an NSInputStream object 8
Listing 2 Handling a bytes-available event 10
Listing 3 Closing and releasing the NSInputStream object 11
Writing To Output Streams 12
Listing 1 Creating and initializing an NSOutputStream object for memory 13
Listing 2 Handling a space-available event 14
Listing 3 Closing and releasing the NSInputStream object 15
Polling Versus Run-Loop Scheduling 17
Listing 1 Writing to an output stream using polling 17
Handling Stream Errors 20
Listing 1 Handling stream errors 20
Setting Up Socket Streams 22
Listing 1 Setting up a network socket stream 22
Listing 2 Making an HTTP GET request 24
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
3A stream is a fundamental abstraction in programming: a sequence of bits transmitted serially from one point
to another point. Cocoa provides three classes to represent streams and facilitate their use in your programs:
NSStream, NSInputStream, and NSOutputStream. With the instances of these classes you can read data from,
and write data to, files and application memory. You can also use these objects in socket-based connections
to exchange data with remote hosts. You can also subclass the stream classes to obtain specialized stream
behavior.
Organization of This Document
This document includes the following articles:
●
“Cocoa Streams” (page 6) gives an overview of the Cocoa stream classes, describing architecture,
capabilities, and general usage.
●
“Reading From Input Streams” (page 8) explains how to create and prepare a (non-socket) input-stream
object. It also describes how to handle stream events generated by all types of NSInputStream objects.
●
“Writing To Output Streams” (page 12) explains how to create and prepare a (non-socket) output-stream
object. It also describes how to handle stream events generated by all types of NSOutputStream objects.
●
“Polling Versus Run-Loop Scheduling” (page 17) discusses the relative merits of the two techniques used
to avoid blocking when reading and writing to streams. It also illustrates how to poll for stream data using
the API of the stream classes.
●
“Handling Stream Errors” (page 20) describes how to handle errors that occur in stream processing.
●
“Setting Up Socket Streams” (page 22) explains how to set up stream objects used to communicate with
remote hosts via sockets.
See Also
You may find the following external resources helpful if you are implementing socket-based network streams:
● OpenSSL — http://www.openssl.org/
● Apache SSL — http://www.apache-ssl.org/
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
4
Introduction to Stream Programming Guide for
Cocoa● SOCKS — http://tools.ietf.org/html/rfc1928
Introduction to Stream Programming Guide for Cocoa
See Also
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
5Streams provide an easy way for a program to exchange data with a variety of media in a device-independent
way. A stream is a contiguous sequence of bits transmitted serially over a communications path. It is
unidirectional and hence, from the perspective of a program, a stream can be an input (or read) stream or an
output (or write) stream. Except for ones that are file-based, streams are non-seekable; once stream data has
been provided or consumed, it cannot be retrieved again from the stream.
Cocoa includes three stream-related classes: NSStream, NSInputStream, and NSOutputStream. NSStream
is an abstract classthat definesthe fundamental interface and propertiesfor allstream objects. NSInputStream
and NSOutputStream are subclasses of NSStream and implement default input-stream and output-stream
behavior. You can create NSOutputStream instances for stream data located in memory or written to a file
or C buffer; you can create NSInputStream instances for stream data read from an NSData object or a file.
You can also have NSInputStream and NSOutputStream objects at the end points of a socket-based network
connection and you can use stream objects without loading all of the stream data into memory at once. Figure
1 illustrates the types of input-stream and output-stream objects in terms of their sources or destinations.
Figure 1 Sources and destinations of stream objects
NSOutputStream
Buffer
Memory
(NSData)
Network
socket
Data
(NSData)
Network
socket
Client program
NSInputStream
File File
Because they deal with such a basic computing abstraction (streams), NSStream and itssubclasses are intended
for lower-level programming tasks. If there is a higher-level Cocoa API that is more suited for a particular task
(for example, NSURL or NSFileHandle) use it instead.
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
6
Cocoa StreamsStream objects have properties associated with them. Most properties have to do with network security and
configuration, namely secure-socket (SSL) levels and SOCKS proxy information. Two important additional
properties are NSStreamDataWrittenToMemoryStreamKey, which permits retrieval of data written to
memory for an output stream, and NSStreamFileCurrentOffsetKey, which allows you to manipulate the
current read or write position in file-based streams.
A stream object also has a delegate associated with it. If a delegate is not explicitly set, the stream object itself
becomesthe delegate (a useful convention for custom subclasses). A stream object invokesthe sole delegation
method stream:handleEvent: for each stream-related event it handles. Of particular importance are the
events that indicate when bytes are available to read from an input stream and when an output stream signals
that it’s ready to accept bytes. For these two events, the delegate sends the stream the appropriate
message—read:maxLength: or write:maxlength:, depending on type of stream—to get the bytes from
the stream or to put bytes on the stream.
NSStream is built on the CFStream layer of Core Foundation. This close relationship means that the concrete
subclasses of NSStream, NSOutputStream and NSInputStream, are toll-free bridged with their Core
Foundation counterparts CFWriteStream and CFReadStream. Although there are strong similarities between
the Cocoa and Core Foundation stream APIs, their implementations are not exactly coincident. The Cocoa
stream classes use the delegation model for asynchronous behavior (assuming run-loop scheduling) while
Core Foundation uses client callbacks. The Core Foundation stream types sets the client (termed a context in
Core Foundation) differently than the NSStream setsthe delegate; callsto set the delegate should not be mixed
with calls to set the context. Otherwise you can freely intermix calls from the two APIs in your code.
Despite their strong similarities, NSStream does give you a major advantage over CFStream. Because of its
Objective-C underpinnings, it is extensible. You can subclass NSStream, NSInputStream, or NSOutputStream
to customize stream attributes and behavior. For example, you could create an input stream that maintains
statistics on the bytes it reads; or you could make a NSStream subclass whose instances can seek through
their stream, putting back bytes that have been read. NSStream has its own set of required overrides, as do
NSInputStream and NSOutputStream. See the reference documentation for NSStream, NSInputStream,
and NSOutputStream for details on subclassing these classes.
Cocoa Streams
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
7In Cocoa, reading from an NSInputStream instance consists of several steps:
1. Create and initialize an instance of NSInputStream from a source of data.
2. Schedule the stream object on a run loop and open the stream.
3. Handle the events that the stream object reports to its delegate.
4. When there is no more data to read, dispose of the stream object.
The following discussion goes into each of these steps in more detail.
Note: The examples in this document show the strategy of scheduling stream objects on run loops
and setting a delegate to handle stream events. You may use polling instead of run-loop scheduling
if you prefer that approach. However, run-loop scheduling with delegation isthe preferred approach
for various reasons (described in “Polling Versus Run-Loop Scheduling” (page 17)), and that is why
it is highlighted in this document.
Preparing the Stream Object
To begin using an NSInputStream object you must have (after first locating, if necessary) a source of data
for the stream. The source of data can be a file, an NSData object, or a network socket.
Note: The procedure for initializing input-stream objects from network sockets is different from the
procedure for the other two data sources, and is not covered in this article. To learn about initializing
an NSInputStream instance for a network connection, see “Setting Up Socket Streams” (page 22).
The initializers and factory methods for NSInputStream allow you to create and initialize the instance from an
NSData or file. Listing 1 shows an NSInputStream instance created from a file.
Listing 1 Creating and initializing an NSInputStream object
- (void)setUpStreamForFile:(NSString *)path {
// iStream is NSInputStream instance variable
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
8
Reading From Input StreamsiStream = [[NSInputStream alloc] initWithFileAtPath:path];
[iStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[iStream open];
}
As this example shows, after you create the object you should set the delegate (more often than not to self).
The delegate receives stream:handleEvent: messages from the NSInputStream object when that object is
scheduled on the run loop and hasstream-related eventsto report,such as when there are bytes on the stream
to be read.
Before you open the stream to begin the streaming of data,send a scheduleInRunLoop:forMode: message
to the stream object to schedule it to receive stream events on a run loop. By doing this, you are helping the
delegate to avoid blocking when there is no data on the stream to read. If streaming is taking place on another
thread, be sure to schedule the stream object on that thread’s run loop. You should never attempt to access
a scheduled stream from a thread different than the one owning the stream’s run loop. Finally, send the
NSInputStream instance an open message to start the streaming of data from the input source.
Handling Stream Events
After a stream object is sent open, you can find out about its status, whether it has bytes available to read,
and the nature of any error with the following messages:
streamStatus
hasBytesAvailable
streamError
The returned status is an NSStreamStatus constant indicating that the stream is opening, reading, at the
end of the stream, and so on. The returned error is an NSError object encapsulating information about any
error that took place. (See the reference documentation for NSStream for descriptions of NSStreamStatus
and other stream types.)
More importantly, once the streamobject has been opened, it keepssending stream:handleEvent:messages
to its delegate until it encounters the end of the stream. These messages include a parameter with an
NSStreamEvent constant that indicates the type of event. For NSInputStream objects, the most common
types of events are NSStreamEventOpenCompleted, NSStreamEventHasBytesAvailable, and
Reading From Input Streams
Handling Stream Events
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
9NSStreamEventEndEncountered. The delegate is typically most interested in
NSStreamEventHasBytesAvailable events. Listing 2 illustrates a good approach for handling this type of
event.
Listing 2 Handling a bytes-available event
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventHasBytesAvailable:
{
if(!_data) {
_data = [[NSMutableData data] retain];
}
uint8_t buf[1024];
unsigned int len = 0;
len = [(NSInputStream *)stream read:buf maxLength:1024];
if(len) {
[_data appendBytes:(const void *)buf length:len];
// bytesRead is an instance variable of type NSNumber.
[bytesRead setIntValue:[bytesRead intValue]+len];
} else {
NSLog(@"no buffer!");
}
break;
}
// continued
In this implementation of stream:handleEvent: the delegate uses a switch statement to identify the
passed-in NSStreamEvent constant. If the constant is NSStreamEventHasBytesAvailable, the delegate
first lazily creates(if necessary) an NSMutableData object (_data) to hold the retrieved bytes. Then it declares
a buffer of a certain size (1024 bytes, in this case) and invokes the stream object’s read:maxLength: method,
which fills up the buffer with the specified number of bytes. If the read operation successfully fetched bytes
from the stream, the delegate appends these bytes to the NSMutableData object.
Reading From Input Streams
Handling Stream Events
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
10There is no firm guideline on how many bytes to read at one time. Although it may be possible to read all the
data in the stream in one event, this depends on the length of the stream (that is, the number of bytes in it)
as well as the behavior of the kernel, including device and socket characteristics. The best approach is to use
some reasonable buffer size, such as 512 bytes, one kilobyte (as in the example above), or a page size (four
kilobytes).
When the NSInputStream object experiences errors processing the stream, it stops streaming and notifies
its delegate with a NSStreamEventErrorOccurred. The delegate should handle the error in its
stream:handleEvent: method as described in “Handling Stream Errors” (page 20).
Disposing of the Stream Object
When an NSInputStream object reaches the end of a stream, it sends the delegate a
NSStreamEventEndEncountered event in a stream:handleEvent: message. The delegate should dispose
of the object by doing the mirror-opposite of what it did to prepare the object. In other words, it should first
close the stream object, remove it from the run loop, and finally release it. Listing 3 gives an example of how
you might do this.
Listing 3 Closing and releasing the NSInputStream object
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
{
switch(eventCode) {
case NSStreamEventEndEncountered:
{
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[stream release];
stream = nil; // stream is ivar, so reinit it
break;
}
// continued ...
}
}
Reading From Input Streams
Disposing of the Stream Object
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
11Using an NSOutputStream instance to write to an output stream requires several steps:
1. Create and initialize an instance of NSOutputStream with a repository for the written data. Also set a
delegate.
2. Schedule the stream object on a run loop and open the stream.
3. Handle the events that the stream object reports to its delegate.
4. If the stream object has written data to memory, obtain the data by requesting the
NSStreamDataWrittenToMemoryStreamKey property.
5. When there is no more data to write, dispose of the stream object.
The following discussion goes into each of these steps in more detail.
Note: The examples in this document show the strategy of scheduling stream objects on run loops
and setting a delegate to handle stream events. You may use polling instead of run-loop scheduling
if you prefer that approach. However, run-loop scheduling with delegation isthe preferred approach
for various reasons (described in “Polling Versus Run-Loop Scheduling” (page 17)), and that is why
it is highlighted in this document.
Preparing the Stream Object
To begin using an NSOutputStream object you must specify a destination for the data written to the stream.
The destination for an output-stream object can be a file, a C buffer, application memory, or a network socket.
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
12
Writing To Output StreamsNote: The procedure for initializing output-stream objects from network sockets is different from
the procedure for the other data destinations, and is not covered in this article. To learn about
initializing an NSOutputStream instance for a network connection, see “Setting Up Socket
Streams” (page 22).
The initializers and factory methods for NSOutputStream allow you to create and initialize the instance with
a file, a buffer, or memory. Listing 1 shows the creation of an NSOutputStream instance that will write data
to application memory.
Listing 1 Creating and initializing an NSOutputStream object for memory
- (void)createOutputStream {
NSLog(@"Creating and opening NSOutputStream...");
// oStream is an instance variable
oStream = [[NSOutputStream alloc] initToMemory];
[oStream setDelegate:self];
[oStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[oStream open];
}
As the code in Listing 1 shows, after you create the object you should set the delegate (more often than not
to self). The delegate receives stream:handleEvent: messages from the NSOutputStream object when
that object has stream-related events to report, such as when the stream has space for bytes.
Before you open the stream to begin the streaming of data,send a scheduleInRunLoop:forMode: message
to the stream object to schedule it to receive stream events on a run loop. By doing this, you are helping the
delegate to avoid blocking when the stream is unable to accept more bytes. If streaming is taking place on
another thread, be sure to schedule the stream object on that thread’s run loop. You should never attempt to
access a scheduled stream from a thread different than the one owning the stream’s run loop. Finally, send
the NSOutputStream instance an open message to start the streaming of data to the output container.
Handling Stream Events
After a stream object is sent open, you can find out about its status, whether it has space for writing data, and
the nature of any error with the following messages:
streamStatus
Writing To Output Streams
Handling Stream Events
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
13hasSpaceAvailable
streamError
The returned status is an NSStreamStatus constant indicating that the stream is opening, writing, at the end
of the stream, and so on. The returned error is an NSError object encapsulating information about any error
that took place. (See the reference documentation for NSStream for descriptions of NSStreamStatus and
other stream types.)
More importantly, once the streamobject has been opened, it keepssending stream:handleEvent:messages
to its delegate (as long as the delegate continues to put bytes on the stream) until it encounters the end of
the stream. These messages include a parameter with an NSStreamEvent constant that indicates the type of
event. For NSOutputStream objects, the most common types of events are NSStreamEventOpenCompleted,
NSStreamEventHasSpaceAvailable, and NSStreamEventEndEncountered. The delegate is typically
most interested in NSStreamEventHasSpaceAvailable events. Listing 2 illustrates one approach you could
take to handle this type of event.
Listing 2 Handling a space-available event
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
{
switch(eventCode) {
case NSStreamEventHasSpaceAvailable:
{
uint8_t *readBytes = (uint8_t *)[_data mutableBytes];
readBytes += byteIndex; // instance variable to move pointer
int data_len = [_data length];
unsigned int len = ((data_len - byteIndex >= 1024) ?
1024 : (data_len-byteIndex));
uint8_t buf[len];
(void)memcpy(buf, readBytes, len);
len = [stream write:(const uint8_t *)buf maxLength:len];
byteIndex += len;
break;
}
// continued ...
}
}
Writing To Output Streams
Handling Stream Events
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
14In this implementation of stream:handleEvent: the delegate uses a switch statement to identify the
passed-in NSStreamEvent constant. If the constant is NSStreamEventHasSpacesAvailable, the delegate
gets the bytes held by a NSMutableData object (_data) and advances the pointer for the current write
operation. It next determines the byte capacity of the impending write operation (1024 or the remaining bytes
to write), declares a buffer of that size, and copies that amount of data to the buffer. Next the delegate invokes
the output-stream object’s write:maxLength: method to put the buffer’s contents onto the output stream.
Finally it advances the index used to advance the readBytes pointer for the next operation.
If the delegate receives an NSStreamEventHasSpaceAvailable event and does not write anything to the
stream, it does not receive further space-available events from the run loop until the NSOutputStream object
receives more bytes. When this happens, the run loop is restarted for space-available events. If this scenario
is likely in your implementation, you can have the delegate set a flag when it doesn’t write to the stream upon
receiving an NSStreamEventHasSpaceAvailable event. Later, when your program has more bytesto write,
it can check this flag and, if set, write to the output-stream instance directly.
There is no firm guideline on how many bytes to write at one time. Although it may be possible to write all
the data to the stream in one event, this depends on external factors, such as the behavior of the kernel and
device and socket characteristics. The best approach is to use some reasonable buffer size, such as 512 bytes,
one kilobyte (as in the example above), or a page size (four kilobytes).
When the NSOutputStream object experiences errors writing to the stream, it stops streaming and notifies
its delegate with a NSStreamEventErrorOccurred. The delegate should handle the error in its
stream:handleEvent: method as described in “Handling Stream Errors” (page 20).
Disposing of the Stream Object
When an NSOutputStream object concludes writing data to an output stream, it sends the delegate a
NSStreamEventEndEncountered event in a stream:handleEvent: message. At this point the delegate
should dispose of the stream object by doing the mirror-opposite of what it did to prepare the object. In other
words, it should first close the stream object, remove it from the run loop, and finally release it. Furthermore,
if the destination for the NSOutputStream object is application memory (that is, you created the instance
using initToMemory or the factory method outputStreamToMemory), you might now want to retrieve the
data held in memory. Listing 3 illustrates how you might do all of these things.
Listing 3 Closing and releasing the NSInputStream object
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
{
switch(eventCode) {
case NSStreamEventEndEncountered:
Writing To Output Streams
Disposing of the Stream Object
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
15{
NSData *newData = [oStream propertyForKey:
NSStreamDataWrittenToMemoryStreamKey];
if (!newData) {
NSLog(@"No data written to memory!");
} else {
[self processData:newData];
}
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[stream release];
oStream = nil; // oStream is instance variable
break;
}
// continued ...
}
}
You get the stream data written to memory by sending the NSOutputStream object a propertyForKey:
message, specifying a key of NSStreamDataWrittenToMemoryStreamKey The stream object returns the
data in an NSData object.
Writing To Output Streams
Disposing of the Stream Object
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
16A potential problem with stream processing is blocking. A thread that is writing to or reading from a stream
might have to wait indefinitely until there is (respectively) space on the stream to put bytes or bytes on the
stream that can be read. In effect, the thread is at the mercy of the stream, and that can spell trouble for an
application. Blocking can especially be a problem with socketstreams because they are dependent on responses
from a remote host.
With Cocoa streams you have two ways to handle stream events:
● Run-loop scheduling. You schedule a stream object on a run loop so that the delegate receives messages
reporting stream-related events only when blocking is unlikely to take place. For read and write operations,
the pertinent NSStreamEvent constants are NSStreamHasBytesAvailable and
NSStreamHasSpaceAvailable.
● Polling. In a closed loop broken only at the end of the stream or upon error, you keep asking the stream
object if it has (for read streams) bytes available to read or (for write streams) space available for writing.
The pertinent methods are hasBytesAvailable (NSInputStream) and hasSpaceAvailable
(NSOutputStream).
Run-loop scheduling is almost always preferable over polling, and that is why the code examples in “Reading
From Input Streams” (page 8) and “Writing To Output Streams” (page 12) exclusively show the use of run
loops. With polling, your program is locked in a tight loop, waiting for stream events that might or might not
be imminent. With run-loop scheduling, your program can go off and do other things, knowing that it will be
notified when there is a stream event to handle. Moreover, run loops save you from having to manage state
and are more efficient than polling. Polling is also CPU-intensive; there are other things you can be doing with
your processing time.
That said, there can be situations where polling is a viable option. For example, if you are porting legacy code,
you might choose to use polling because it is better suited the threading model in the legacy code. Listing 1
illustrates a method that writes data to an output stream using polling.
Listing 1 Writing to an output stream using polling
- (void)createNewFile {
oStream = [[NSOutputStream alloc] initToMemory];
[oStream open];
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
17
Polling Versus Run-Loop Schedulinguint8_t *readBytes = (uint8_t *)[data mutableBytes];
uint8_t buf[1024];
int len = 1024;
while (1) {
if (len == 0) break;
if ( [oStream hasSpaceAvailable] ) {
(void)strncpy(buf, readBytes, len);
readBytes += len;
if ([oStream write:(const uint8_t *)buf maxLength:len] == -1) {
[self handleError:[oStream streamError]];
break;
}
[bytesWritten setIntValue:[bytesWritten intValue]+len];
len = (([data length] - [bytesWritten intValue] >= 1024) ? 1024 :
[data length] - [bytesWritten intValue]);
}
}
NSData *newData = [oStream propertyForKey:
NSStreamDataWrittenToMemoryStreamKey];
if (!newData) {
NSLog(@"No data written to memory!");
} else {
[self processData:newData];
}
[oStream close];
[oStream release];
oStream = nil;
}
It should be pointed out that neither the polling nor run-loop scheduling approaches are airtight defenses
against blocking. If the NSInputStream hasBytesAvailable method or the NSOutputStream
hasSpaceAvailable method returns NO, it means in both cases that the stream definitely has no available
bytes or space. However, if either of these methods returns YES, it can mean that there is available bytes or
Polling Versus Run-Loop Scheduling
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
18space or that the only way to find out isto attempt a read or a write operation (which could lead to a momentary
block). The NSStreamEventHasBytesAvailable and NSStreamEventHasSpaceAvailable stream events
have identical semantics.
Polling Versus Run-Loop Scheduling
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
19Occasionally, and especially with sockets, streams can experience errors that prevent further processing of
stream data. Generally, errors indicate the absence of something at one end of a stream, such as the crash of
a remote host or the deletion of a file being streamed. There is a little that a client of a stream can do when
most errors occur except report the error to the user. Although a stream object that has reported an error can
be queried for state before it is closed, it cannot be reused for read or write operations.
The NSStream and NSOutputStream classes inform you if an error occurred in several ways:
●
If the stream object is scheduled on a run loop, the object reports a NSStreamEventErrorOccurred
event to its delegate in a stream:handleEvent: message.
● At any time, the client can send a streamStatus message to a stream object and see if it returns
NSStreamStatusError.
●
If you attempt to write to an NSOutputStream object by sending it write:maxLength: and it returns
-1, a write error has occurred.
Once you have determined that a stream object experienced an error, you can query the object with a
streamError message to get more information about the error (in the form of an NSError object). Next,
inform the user about the error. Listing 1 shows how the delegate of a run loop-scheduled stream object might
handle an error.
Listing 1 Handling stream errors
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
NSLog(@"stream:handleEvent: is invoked...");
switch(eventCode) {
case NSStreamEventErrorOccurred:
{
NSError *theError = [stream streamError];
NSAlert *theAlert = [[NSAlert alloc] init];
[theAlert setMessageText:@"Error reading stream!"];
[theAlert setInformativeText:[NSString stringWithFormat:@"Error %i:
%@",
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
20
Handling Stream Errors[theError code], [theError localizedDescription]]];
[theAlert addButtonWithTitle:@"OK"];
[theAlert beginSheetModalForWindow:[NSApp mainWindow]
modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
contextInfo:nil];
[stream close];
[stream release];
break;
}
// continued ....
}
}
For some errors, you can attempt to do more than inform the user. For example, if you try to set an SSL security
level on a socket connection but the remote host is not secure, the stream object will report an error. You can
then release the old stream object and create a new one for a non-secure socket connection.
Handling Stream Errors
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
21You can use the CFStream API to establish a socket connection and, with the stream object (or objects) created
as a result,send data to and receive data from a remote host. You can also configure the connection forsecurity.
Basic Procedure
The NSStream class does notsupport connecting to a remote host on iOS. CFStream does support this behavior,
however, and once you have created your streams with the CFStream API, you can take advantage of the
toll-free bridge between CFStream and NSStream to cast your CFStreams to NSStreams. Just call the
CFStreamCreatePairWithSocketToHost function, providing a host name and a port number, to receive
both a CFReadStreamRef and a CFWriteStreamRef for the given host. You can then cast these objects to
an NSInputStream and an NSOutputStream and proceed.
Listing 1 illustrates the use of CFStreamCreatePairWithSocketToHost. This example shows the creation
of both a CFReadStreamRef object and a CFWriteStreamRef object. If you want to receive only one of
these objects, just specify NULL as the parameter value for the unwanted object.
Listing 1 Setting up a network socket stream
- (IBAction)searchForSite:(id)sender
{
NSString *urlStr = [sender stringValue];
if (![urlStr isEqualToString:@""]) {
NSURL *website = [NSURL URLWithString:urlStr];
if (!website) {
NSLog(@"%@ is not a valid URL");
return;
}
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)[website host], 80,
&readStream, &writeStream);
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
22
Setting Up Socket StreamsNSInputStream *inputStream = (__bridge_transfer NSInputStream *)readStream;
NSOutputStream *outputStream = (__bridge_transfer NSOutputStream
*)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
/* Store a reference to the input and output streams so that
they don't go away.... */
...
}
}
If you pass in invalid parameters, one or both of the requested CFReadStreamRef and CFWriteStreamRef
objects are NULL. Once you have cast the CFStreams to NSStreams, set the delegate, schedule the stream on a
run loop, and open the stream as usual. The delegate should begin to receive stream-event messages
(stream:handleEvent:). See “Reading From Input Streams” (page 8) and “Writing To Output Streams” (page
12) for more information.
Securing and Configuring the Connection
Before you open a stream object, you might want to set security and other features for the connection to the
remote host (which might be, for example, an HTTPS server). NSStream defines properties that affect the
security of TCP/IP socket connections in two ways:
● Secure Socket Layer (SSL).
A security protocol using digital certificates to provide data encryption, server authentication, message
integrity, and (optionally) client authentication for TCP/IP connections.
● SOCKS proxy server.
Setting Up Socket Streams
Securing and Configuring the Connection
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
23A server that sits between a client application and a real server over a TCP/IP connection. It intercepts
requests to the real server and, if it cannot fulfill them from a cache of recently requested files, forwards
them to the real server. SOCKS proxy servers help improve performance over a network and can also be
used to filter requests.
For SSL security, NSStream defines various security-level properties (for example,
NSStreamSocketSecurityLevelSSLv2). You set these properties by sending setProperty:forKey: to
the stream object using the key NSStreamSocketSecurityLevelKey, as in this sample message:
[inputStream setProperty:NSStreamSocketSecurityLevelTLSv1
forKey:NSStreamSocketSecurityLevelKey];
You must set the property before you open the stream. Once it opens, it goes through a handshake protocol
to find out what level of SSL security the other side of the connection is using. If the security level is not
compatible with the specified property, the stream object generates an error event. However, if you request
a negotiated security level (NSStreamSocketSecurityLevelNegotiatedSSL), the security level becomes
the highest that both sides of the connection can implement. Still, if you try to set an SSL security level when
the remote host is not secure, an error is generated.
To configure a SOCKS proxy server for a connection, you need to construct a dictionary with keys of the form
NSStreamSOCKSProxyNameKey (for example, NSStreamSOCKSProxyHostKey). The value of each key is
the SOCKS proxy setting that Name refers to. Then using setProperty:forKey:, set the dictionary as the
value of the NSStreamSOCKSProxyConfigurationKey.
Initiating an HTTP Request
If you are opening a connection to an HTTP server (that is, a website), then you may have to initiate a transaction
with that server by sending it an HTTP request. A good time to make this request is when the delegate of the
NSOutputStream objectreceives a NSStreamEventHasSpaceAvailable event via a stream:handleEvent:
message. “Making an HTTP GET request” shows the delegate creating an HTTP GET request and writing it to
the output stream, after which it immediately closes the stream object.
Listing 2 Making an HTTP GET request
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
NSLog(@"stream:handleEvent: is invoked...");
switch(eventCode) {
Setting Up Socket Streams
Initiating an HTTP Request
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
24case NSStreamEventHasSpaceAvailable:
{
if (stream == oStream) {
NSString * str = [NSString stringWithFormat:
@"GET / HTTP/1.0\r\n\r\n"];
const uint8_t * rawstring =
(const uint8_t *)[str UTF8String];
[oStream write:rawstring maxLength:strlen(rawstring)];
[oStream close];
}
break;
}
// continued ...
}
}
For More Information
To learn more about using streams for networking, read Networking Overview.
Setting Up Socket Streams
For More Information
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
25This table describes the changes to Stream Programming Guide .
Date Notes
2012-09-19 Clarified behavior of CFStreamCreatePairWithSocketToHost.
2009-12-16 Updated code listings in the Setting Up Socket Streams chapter.
2009-08-28 Added links to related concepts.
2009-05-06 Added a missing comment to a code sample.
2008-10-15 Fixed broken links.
2006-10-03 Fixed a broken link.
Changed event in code listing on writing to a network stream to
NSStreamEventHasSpaceAvailable.
2006-04-04
2005-07-07 Fixed bugs and changed title from "Streams."
2004-07-21 Fixed bug in code example (Radar 3597799).
2004-02-20 First version of Streams.
2012-09-19 | © 2004, 2012 Apple Inc. All Rights Reserved.
26
Document Revision HistoryApple Inc.
© 2004, 2012 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Cocoa, Mac, Objective-C,
and Spaces are trademarks of Apple Inc.,
registered in the U.S. and other countries.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
URL Loading System
Programming GuideContents
Introduction 5
Organization of This Document 5
See Also 6
URL Loading System Overview 7
URL Loading 7
Cache Management 9
Authentication and Credentials 9
Cookie Storage 10
Protocol Support 11
Using NSURLConnection 12
Creating a Connection 12
Controlling Response Caching 15
Estimating Upload Progress 16
Downloading Data Synchronously 17
Using NSURLDownload 18
Downloading to a Predetermined Destination 18
Downloading a File Using the Suggested Filename 20
Displaying Download Progress 22
Resuming Downloads 24
Decoding Encoded Files 24
Handling Redirects and Other Request Changes 26
Authentication Challenges 28
Deciding How to Respond to an Authentication Challenge 28
Responding to an Authentication Challenge 29
Providing Credentials 29
Continuing Without Credentials 30
Canceling the Connection 30
Understanding Cache Access 32
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
2Using the Cache for a Request 32
Cache Use Semantics for the http Protocol 33
Document Revision History 34
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
3Figures and Listings
URL Loading System Overview 7
Figure 1 The URL loading system class hierarchy 7
Using NSURLConnection 12
Listing 1 Creating a connection using NSURLConnection 12
Listing 2 Example connection:didReceiveResponse: implementation 13
Listing 3 Example connection:didReceiveData: implementation 14
Listing 4 Example connectionDidFailWithError: implementation 14
Listing 5 Example connectionDidFinishLoading: implementation 15
Listing 6 Example connection:withCacheResponse: implementation 16
Using NSURLDownload 18
Listing 1 Using NSURLDownload with a predetermined destination file location 18
Listing 2 Using NSURLDownload with a filename derived from the download 20
Listing 3 Logging the finalized filename using download:didCreateDestination: 22
Listing 4 Displaying the download progress 22
Listing 5 Example implementation of download:shouldDecodeSourceDataOfMIMEType: method. 24
Handling Redirects and Other Request Changes 26
Listing 1 Example of an implementation of connection:willSendRequest:redirectResponse:
26
Authentication Challenges 28
Listing 1 An example of using the connection:didReceiveAuthenticationChallenge: delegate
method 30
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
4This guide describes the Foundation framework classes available for interacting with URLs and communicating
with servers using standard Internet protocols. Together these classes are referred to asthe URL loading system.
The NSURL class provides the ability to manipulate URLs and the resources they refer to.
The Foundation framework also provides a rich collection of classes that include support for URL loading,
cookie storage, response caching, credentialstorage and authentication, and writing custom protocol extensions.
The URL loading system provides support for accessing resources using the following protocols:
● File Transfer Protocol (ftp://)
● Hypertext Transfer Protocol (http://)
● Secure 128-bit Hypertext Transfer Protocol (https://)
● Local file URLs (file:///)
It also transparently supports both proxy servers and SOCKS gateways using the user’s system preferences.
Organization of This Document
This guide includes the following articles:
●
“URL Loading System Overview” (page 7) describes the classes of the URL loading system and their
interaction.
●
“Using NSURLConnection” (page 12) describes using NSURLConnection for asynchronous connections.
●
“Using NSURLDownload” (page 18) describes using NSURLDownload to download files asynchronously
to disk.
●
“Handling Redirects and Other Request Changes” (page 26) describesthe options you have for responding
to a change to your URL request.
●
“Authentication Challenges” (page 28) describes the process for authenticating your connection against
a secure server.
●
“Understanding Cache Access” (page 32) describes how a connection uses the cache during a request.
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
5
IntroductionSee Also
The following sample code is available through Apple Developer Connection:
● SpecialPictureProtocol implements a custom NSURLProtocol that creates jpeg images in memory as
data is downloaded.
● AutoUpdater demonstrates how to check for, and download, an application update using
NSURLConnection and NSURLDownload.
Introduction
See Also
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
6The URL loading system is a set of classes and protocols that provide the underlying capability for an application
to access the data specified by a URL.
These classes fall into five categories: URL loading, cache management, authentication and credentials, cookie
storage, and protocol support.
Figure 1 The URL loading system class hierarchy
NSObject
URL Loading
NSURLConnection
NSURLRequest NSMutableURLRequest
NSURLResponse NSHTTPURLResponse
NSURLDownload
Cache Management
NSCacheURLRequest
NSURLCache
Cookie Storage
NSHTTPCookie
NSHTTPCookieStorage
Protocol Support
NSURLProtocolClient
NSURLProtocol
Authentication and Credentials
NSURLCredential
NSURLCredentialStorage
NSURLProtectionSpace
NSURLAuthenticationChallenge
NSURLAuthenticationChallengeSender
URL Loading
The most commonly used classes in the URL loading system allow an application to create a request for the
content of a URL and download it from the source.
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
7
URL Loading System OverviewA request for the contents of a URL is represented by an NSURLRequest object. The NSURLRequest class
encapsulates a URL and any protocol-specific properties, in a protocol-independent manner. It also provides
an interface to set the timeout for a connection and specifiesthe policy regarding the use of any locally cached
data. The NSMutableURLRequest classis a mutable subclass of NSURLRequest that allows a client application
to alter an existing request.
Note: When a client application initiates a connection or download using an instance of
NSMutableURLRequest, a deep copy is made of the request. Changes made to the initiating request
have no effect once a download has been initialized.
Protocols,such as HTTP, thatsupport protocol-specific properties must create categories on the NSURLRequest
and NSMutableURLRequest classesto provide accessorsfor those properties. As an example, the HTTP protocol
adds methods to NSURLRequest to return the HTTP request body, headers, and transfer method. It also adds
methodsto NSMutableURLRequest to set the corresponding values. Methodsforsetting and getting property
values in those accessors are exposed in the NSURLProtocol class.
The response from a server to a request can be viewed as two parts: metadata describing the contents and
the URL content data. The metadata that is common to most protocolsis encapsulated by the NSURLResponse
class and consists of the MIME type, expected content length, text encoding (where applicable), and the URL
that provided the response. Protocols can create subclasses of NSURLResponse to store protocol-specific
metadata. NSHTTPURLResponse, for example, stores the headers and the status code returned by the web
server.
Note: It’s important to remember that only the metadata for the response is stored in an
NSURLResponse object. An NSCachedURLResponse instance is used to encapsulate an
NSURLResponse, the URL content data, and any application-provided information. See “Cache
Management” (page 9) for details.
The NSURLConnection and NSURLDownload classes provide the interface to make a connection specified
by an NSURLRequest object and download the contents. An NSURLConnection object provides data to the
delegate as it is received from the originating source, whereas an NSURLDownload object writes the request
data directly to disk. Both classes provide extensive delegate support for responding to redirects, authentication
challenges, and error conditions.
The NSURLConnection class provides a delegate method that allows an application to control the caching
of a response on a per-request basis. Downloads initiated by an NSURLDownload instance are not cached.
URL Loading System Overview
URL Loading
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
8Cache Management
The URL loading system provides a composite on-disk and in-memory cache allowing an application to reduce
its dependence on a network connection and provide faster turnaround for previously cached responses. The
cache is stored on a per-application basis.
The cache is queried by NSURLConnection according to the cache policy specified by the initiating
NSURLRequest.
The NSURLCache class provides methods to configure the cache size and its location on disk. It also provides
methods to manage the collection of NSCachedURLResponse objects that contain the cached responses.
An NSCachedURLResponse object encapsulates the NSURLResponse and the URL content data.
NSCachedURLResponse also provides a user info dictionary that can be used by an application to cache any
custom data.
Not all protocol implementations support response caching. Currently only http and https requests are
cached, and https requests are never cached to disk.
An NSURLConnection can control whether a response is cached and whether the response should be cached
only in memory by implementing the connection:willCacheResponse: delegate method.
Authentication and Credentials
Some servers restrict access to certain content, requiring a user to authenticate with a valid user name and
password in order to gain access. In the case of a web server, restricted content is grouped together into a
realm that requires a single set of credentials.
The URL loading system provides classesthat model credentials and protected areas as well as providing secure
credential persistence. Credentials can be specified to persist for a single request, for the duration of an
application’s launch, or permanently in the user’s keychain.
Note: Credentials stored in persistent storage are kept in the user's keychain and shared among all
applications.
The NSURLCredential class encapsulates a credential consisting of the user name, password, and the type
of persistence to use. The NSURLProtectionSpace classrepresents an area that requires a specific credential.
A protection space can be limited to a single URL, encompass a realm on a web server, or refer to a proxy.
URL Loading System Overview
Cache Management
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
9A shared instance of the NSURLCredentialStorage class manages credential storage and provides the
mapping of an NSURLCredential object to the corresponding NSURLProtectionSpace object for which
it provides authentication.
The NSURLAuthenticationChallenge class encapsulates the information required by an NSURLProtocol
implementation to authenticate a request: a proposed credential, the protection space involved, the error or
response that the protocol used to determine that authentication isrequired, and the number of authentication
attemptsthat have been made. An NSURLAuthenticationChallenge instance also specifiesthe object that
initiated the authentication. The initiating object, referred to as the sender, must conform to the
NSURLAuthenticationChallengeSender protocol.
NSURLAuthenticationChallenge instances are used by NSURLProtocol subclasses to inform the URL
loading system that authentication is required. They are also provided to the delegate methods of
NSURLConnection and NSURLDownload that facilitate customized authentication handling.
Cookie Storage
Due to the stateless nature of the HTTP protocol, cookies are often used to provide persistent storage of data
across URL requests. The URL loading system provides interfaces to create and manage cookies as well as
sending and receiving cookies from web servers.
The NSHTTPCookie class encapsulates a cookie, providing accessors for many of the common cookie attributes.
It also provides methods to convert HTTP cookie headers to NSHTTPCookie instances and convert an
NSHTTPCookie instance to headers suitable for use with an NSURLRequest. The URL loading system
automatically sends any stored cookies appropriate for an NSURLRequest. unless the request specifies not to
send cookies. Likewise, cookies returned in an NSURLResponse are accepted in accordance with the current
cookie acceptance policy.
The NSHTTPCookieStorage class provides the interface for managing the collection of NSHTTPCookie
objects shared by all applications.
iOS Note: Cookies are not shared by applications in iOS.
NSHTTPCookieStorage allows an application to specify a cookie acceptance policy. The cookie acceptance
policy controls whether cookies should always be accepted, never be accepted, or accepted only from the
same domain as the main document URL.
URL Loading System Overview
Cookie Storage
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
10Note: Changing the cookie acceptance policy in an application affectsthe cookie acceptance policy
for all other running applications.
When another application changesthe cookie storage or the cookie acceptance policy, NSHTTPCookieStorage
notifies an application by posting the NSHTTPCookieStorageCookiesChangedNotification and
NSHTTPCookieStorageAcceptPolicyChangedNotification notifications.
Protocol Support
The URL loading system design allows a client application to extend the protocols that are supported for
transferring data. The URL loading system natively supports http, https, file, and ftp protocols.
Custom protocols are implemented by subclassing NSURLProtocol and then registering the new class with
the URL loading system using the NSURLProtocol class method registerClass:. When an
NSURLConnection or NSURLDownload object initiates a connection for an NSURLRequest, the URL loading
system consults each of the registered classes in the reverse order of their registration. The first class that
returns YES for a canInitWithRequest: message is used to handle the request.
The URL loading system is responsible for creating and releasing NSURLProtocol instances when connections
start and complete. An application should never create an instance of NSURLProtocol directly.
When an NSURLProtocol subclass is initialized by the URL loading system, it is provided a client object that
conforms to the NSURLProtocolClient protocol. The NSURLProtocol subclass sends messages from the
NSURLProtocolClient protocol to the client object to inform the URL loading system of its actions as it creates
a response, receives data, redirectsto a new URL, requires authentication, and completesthe load. If the custom
protocolsupports authentication, then it must conform to the NSURLAuthenticationChallengeSender protocol.
URL Loading System Overview
Protocol Support
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
11NSURLConnection provides the most flexible method of downloading the contents of a URL. It provides a
simple interface for creating and canceling a connection, and supports a collection of delegate methods that
provide feedback and control of many aspects of the connection. These classes fall into five categories: URL
loading, cache management, authentication and credentials, cookie storage, and protocol support.
Creating a Connection
In order to download the contents of a URL, an application needsto provide a delegate object that, at a minimum,
implements the following delegate methods: connection:didReceiveResponse:,
connection:didReceiveData:, connection:didFailWithError: and
connectionDidFinishLoading:.
The example in Listing 1 initiates a connection for a URL. It begins by creating an NSURLRequest instance for
the URL, specifying the cache access policy and the timeout interval for the connection. It then creates an
NSURLConnection instance,specifying the request and a delegate. If NSURLConnection can’t create a connection
for the request, initWithRequest:delegate: returns nil. If the connection is successful, an instance of
NSMutableData is created to store the data that is provided to the delegate incrementally.
Listing 1 Creating a connection using NSURLConnection
// Create the request.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL
URLWithString:@"http://www.apple.com/"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest
delegate:self];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
12
Using NSURLConnectionreceivedData = [[NSMutableData data] retain];
} else {
// Inform the user that the connection failed.
}
The download starts immediately upon receiving the initWithRequest:delegate: message. It can be
canceled any time before the delegate receives a connectionDidFinishLoading: or
connection:didFailWithError: message by sending the connection a cancel message.
When the server has provided sufficient data to create an NSURLResponse object, the delegate receives a
connection:didReceiveResponse: message. The delegate method can examine the provided
NSURLResponse and determine the expected content length of the data, MIME type, suggested filename and
other metadata provided by the server.
You should be prepared for your delegate to receive the connection:didReceiveResponse: message
multiple times for a single connection. This message can be sent due to server redirects, or in rare cases
multi-part MIME documents. Each time the delegate receives the connection:didReceiveResponse:
message, it should reset any progress indication and discard all previously received data. The example
implementation in Listing 2 simply resets the length of the received data to 0 each time it is called.
Listing 2 Example connection:didReceiveResponse: implementation
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse
*)response
{
// This method is called when the server has determined that it
// has enough information to create the NSURLResponse.
// It can be called multiple times, for example in the case of a
// redirect, so each time we reset the data.
// receivedData is an instance variable declared elsewhere.
[receivedData setLength:0];
}
The delegate is periodically sent connection:didReceiveData: messages as the data is received. The
delegate implementation is responsible for storing the newly received data. In the example implementation
in Listing 3, the new data is appended to the NSMutableData object created in Listing 1.
Using NSURLConnection
Creating a Connection
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
13Listing 3 Example connection:didReceiveData: implementation
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// Append the new data to receivedData.
// receivedData is an instance variable declared elsewhere.
[receivedData appendData:data];
}
You can also use the connection:didReceiveData: method to provide an indication of the connection’s
progress to the user.
If an error is encountered during the download, the delegate receives a connection:didFailWithError:
message. The NSError object passed as the parameter specifies the details of the error. It also provides the URL
of the request that failed in the user info dictionary using the key NSURLErrorFailingURLStringErrorKey.
After the delegate receives a message connection:didFailWithError:, it receives no further delegate
messages for the specified connection.
The example in Listing 4 releases the connection, as well as any received data, and logs the error.
Listing 4 Example connectionDidFailWithError: implementation
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
// release the connection, and the data object
[connection release];
// receivedData is declared as a method instance elsewhere
[receivedData release];
// inform the user
NSLog(@"Connection failed! Error - %@ %@",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
Using NSURLConnection
Creating a Connection
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
14Finally, if the connection succeeds in downloading the request, the delegate receives the
connectionDidFinishLoading: message. The delegate will receive no further messagesfor the connection
and the NSURLConnection object can be released.
The example implementation in Listing 5 logsthe length of the received data and releases both the connection
object and the received data.
Listing 5 Example connectionDidFinishLoading: implementation
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// do something with the data
// receivedData is declared as a method instance elsewhere
NSLog(@"Succeeded! Received %d bytes of data",[receivedData length]);
// release the connection, and the data object
[connection release];
[receivedData release];
}
This represents the simplest implementation of a client using NSURLConnection. Additional delegate methods
provide the ability to customize the handling of server redirects, authorization requests and caching of the
response.
Controlling Response Caching
By default the data for a connection is cached according to the support provided by the NSURLProtocolsubclass
that handles the request. An NSURLConnection delegate can further refine that behavior by implementing
connection:willCacheResponse:.
This delegate method can examine the provided NSCachedURLResponse object and change how the response
is cached, for example restricting its storage to memory only or preventing it from being cached altogether.
It is also possible to insert objects in an NSCachedURLResponse’s user info dictionary, causing them to be
stored in the cache as part of the response.
Using NSURLConnection
Controlling Response Caching
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
15Note: The delegate receives connection:willCacheResponse: messages only for protocols
that support caching.
The example in Listing 6 prevents the caching of https responses. It also adds the current date to the user
info dictionary for responses that are cached.
Listing 6 Example connection:withCacheResponse: implementation
-(NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
NSCachedURLResponse *newCachedResponse = cachedResponse;
if ([[[[cachedResponse response] URL] scheme] isEqual:@"https"]) {
newCachedResponse = nil;
} else {
NSDictionary *newUserInfo;
newUserInfo = [NSDictionary dictionaryWithObject:[NSCalendarDate date]
forKey:@"Cached Date"];
newCachedResponse = [[[NSCachedURLResponse alloc]
initWithResponse:[cachedResponse response]
data:[cachedResponse data]
userInfo:newUserInfo
storagePolicy:[cachedResponse storagePolicy]]
autorelease];
}
return newCachedResponse;
}
Estimating Upload Progress
You can estimate the progress of an HTTP POST upload with the
connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite: delegatemethod.
Note that this is not an exact measurement of upload progress, because the connection may fail or the
connection may encounter an authentication challenge.
Using NSURLConnection
Estimating Upload Progress
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
16Downloading Data Synchronously
NSURLConnection provides support for downloading the contents of an NSURLRequest in a synchronous
manner using the class method sendSynchronousRequest:returningResponse:error:. Using this
method is not recommended, because it has severe limitations:
● The client application blocks until the data has been completely received, an error is encountered, or the
request times out.
● Minimal support is provided for requests that require authentication.
● There is no means of modifying the default behavior of response caching or accepting server redirects.
If the download succeeds, the contents of the request are returned as an NSData object and an NSURLResponse
for the request is returned by reference. If NSURLConnection is unable to download the URL, the method
returns nil and any available NSError instance by reference in the appropriate parameter.
If the request requires authentication in order to make the connection, valid credentials must already be
available in the NSURLCredentialStorage, or must be provided as part of the requested URL. If the credentials
are not available or fail to authenticate, the URL loading system responds by sending the NSURLProtocol
subclass handling the connection a continueWithoutCredentialForAuthenticationChallenge:
message.
When a synchronous connection attempt encounters a server redirect, the redirect is always honored. Likewise
the response data is stored in the cache according to the default support provided by the protocol
implementation.
Using NSURLConnection
Downloading Data Synchronously
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
17NSURLDownload provides an application the ability to download the contents of a URL directly to disk. It
provides an interface similar to NSURLConnection, adding an additional method forspecifying the destination
of the file. NSURLDownload can also decode commonly used encoding schemes such as MacBinary, BinHex
and gzip. Unlike NSURLConnection, data downloaded using NSURLDownload is notstored in the cache system.
If your application is not restricted to using Foundation classes, the WebKit framework includes WebDownload,
a subclass of NSURLDownload that provides a user interface for authentication.
iOS Note: The NSURLDownload class is not available in iOS, because downloading directly to the
file system is discouraged. Use the NSURLConnection class instead. See “Using
NSURLConnection” (page 12) for more information.
Downloading to a Predetermined Destination
One usage pattern for NSURLDownload is downloading a file to a predetermined filename on disk. If the
application knows the destination of the download, it can set it explicitly using
setDestination:allowOverwrite:. Multiple setDestination:allowOverwrite: messages to an
NSURLDownload instance are ignored.
The download starts immediately upon receiving the initWithRequest:delegate: message. It can be
canceled any time before the delegate receives a downloadDidFinish: or download:didFailWithError:
message by sending the download a cancel message.
The example in Listing 1 sets the destination, and thus requires the delegate only implement the
download:didFailWithError: and downloadDidFinish: methods.
Listing 1 Using NSURLDownload with a predetermined destination file location
- (void)startDownloadingURL:sender
{
// Create the request.
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL
URLWithString:@"http://www.apple.com"]
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
18
Using NSURLDownloadcachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create the connection with the request and start loading the data.
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest
delegate:self];
if (theDownload) {
// Set the destination file.
[theDownload setDestination:@"/tmp" allowOverwrite:YES];
} else {
// inform the user that the download failed.
}
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
// Release the connection.
[download release];
// Inform the user.
NSLog(@"Download failed! Error - %@ %@",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)downloadDidFinish:(NSURLDownload *)download
{
// Release the connection.
[download release];
// Do something with the data.
NSLog(@"%@",@"downloadDidFinish");
}
Using NSURLDownload
Downloading to a Predetermined Destination
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
19Additional methods can be implemented by the delegate to customize the handling of authentication, server
redirects and file decoding.
Downloading a File Using the Suggested Filename
Sometimesthe application must derive the destination filename from the downloaded data itself. Thisrequires
you to implement the delegate method download:decideDestinationWithSuggestedFilename: and
call setDestination:allowOverwrite: with the suggested filename. The example in Listing 2 saves the
downloaded file to the desktop using the suggested filename.
Listing 2 Using NSURLDownload with a filename derived from the download
- (void)startDownloadingURL:sender
{
// Create the request.
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL
URLWithString:@"http://www.apple.com/index.html"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create the download with the request and start loading the data.
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest
delegate:self];
if (!theDownload) {
// Inform the user that the download failed.
}
}
- (void)download:(NSURLDownload *)download
decideDestinationWithSuggestedFilename:(NSString *)filename
{
NSString *destinationFilename;
NSString *homeDirectory = NSHomeDirectory();
destinationFilename = [[homeDirectory stringByAppendingPathComponent:@"Desktop"]
stringByAppendingPathComponent:filename];
Using NSURLDownload
Downloading a File Using the Suggested Filename
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
20[download setDestination:destinationFilename allowOverwrite:NO];
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
// Release the download.
[download release];
// Inform the user.
NSLog(@"Download failed! Error - %@ %@",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)downloadDidFinish:(NSURLDownload *)download
{
// Release the download.
[download release];
// Do something with the data.
NSLog(@"%@",@"downloadDidFinish");
}
The downloaded file is stored on the user's desktop with the name index.html, which was derived from the
downloaded content. Passing NO to setDestination:allowOverwrite: prevents an existing file from
being overwritten by the download. Instead a unique filename is created by inserting a sequential number
after the filename, for example, index-1.html.
The delegate is informed when a file is created on disk if it implements the
download:didCreateDestination: method. This method also gives the application the opportunity to
determine the finalized filename with which the download is saved.
The example in Listing 3 logs the finalized filename.
Using NSURLDownload
Downloading a File Using the Suggested Filename
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
21Listing 3 Logging the finalized filename using download:didCreateDestination:
-(void)download:(NSURLDownload *)download didCreateDestination:(NSString *)path
{
// path now contains the destination path
// of the download, taking into account any
// unique naming caused by -setDestination:allowOverwrite:
NSLog(@"Final file destination: %@",path);
}
This message is sent to the delegate after it has been given an opportunity to respond to the
download:shouldDecodeSourceDataOfMIMEType: and
download:decideDestinationWithSuggestedFilename: messages.
Displaying Download Progress
The progress of the download can be determined by implementing the delegate methods
download:didReceiveResponse: and download:didReceiveDataOfLength:.
The download:didReceiveResponse: method provides the delegate an opportunity to determine the
expected content length from the NSURLResponse. The delegate should reset the progress each time this
message is received.
The example implementation in Listing 4 demonstrates using these methods to provide progress feedback to
the user.
Listing 4 Displaying the download progress
- (void)setDownloadResponse:(NSURLResponse *)aDownloadResponse
{
[aDownloadResponse retain];
// downloadResponse is an instance variable defined elsewhere.
[downloadResponse release];
downloadResponse = aDownloadResponse;
}
Using NSURLDownload
Displaying Download Progress
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
22- (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse
*)response
{
// Reset the progress, this might be called multiple times.
// bytesReceived is an instance variable defined elsewhere.
bytesReceived = 0;
// Retain the response to use later.
[self setDownloadResponse:response];
}
- (void)download:(NSURLDownload *)download didReceiveDataOfLength:(unsigned)length
{
long long expectedLength = [[self downloadResponse] expectedContentLength];
bytesReceived = bytesReceived + length;
if (expectedLength != NSURLResponseUnknownLength) {
// If the expected content length is
// available, display percent complete.
float percentComplete = (bytesReceived/(float)expectedLength)*100.0;
NSLog(@"Percent complete - %f",percentComplete);
} else {
// If the expected content length is
// unknown, just log the progress.
NSLog(@"Bytes received - %d",bytesReceived);
}
}
The delegate receives a download:didReceiveResponse: message before it begins receiving
download:didReceiveDataOfLength: messages.
Using NSURLDownload
Displaying Download Progress
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
23Resuming Downloads
In some cases, you can resume a download that was canceled or that failed while in progress. To do so, first
make sure your original download doesn’t delete its data upon failure by passing NO to the download’s
setDeletesFileUponFailure: method. If the original download fails, you can obtain its data with the
resumeData method. You can then initialize a new download with the
initWithResumeData:delegate:path: method. When the download resumes, the download’s delegate
receives the download:willResumeWithResponse:fromByte: message.
You can resume a download only if both the protocol of the connection and the MIME type of the file being
downloaded support resuming. You can determine whether your file’s MIME type is supported with the
canResumeDownloadDecodedWithEncodingMIMEType: method.
Decoding Encoded Files
NSURLDownload provides support for decoding selected file formats: MacBinary, BinHex and gzip. If
NSURLDownload determines that a file is encoded in a supported format, it attempts to send the delegate a
download:shouldDecodeSourceDataOfMIMEType: message. If the delegate implements this method, it
should examine the passed MIME type and return YES if the file should be decoded.
The example in Listing 5 compares the MIME type of the file and allows decoding of MacBinary and BinHex
encoded content.
Listing 5 Example implementation of download:shouldDecodeSourceDataOfMIMEType: method.
- (BOOL)download:(NSURLDownload *)download
shouldDecodeSourceDataOfMIMEType:(NSString *)encodingType;
{
BOOL shouldDecode = NO;
if ([encodingType isEqual:@"application/macbinary"]) {
shouldDecode = YES;
} else if ([encodingType isEqual:@"application/binhex"]) {
shouldDecode = YES;
} else if ([encodingType isEqual:@"application/gzip"]) {
shouldDecode = NO;
}
return shouldDecode;
Using NSURLDownload
Resuming Downloads
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
24}
Using NSURLDownload
Decoding Encoded Files
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
25A server may redirect a request for one URL to another URL. The delegates for NSURLConnection and
NSURLDownload can be notified when this occurs for their connection.
To handle a redirect for an instance of NSURLConnection, implement the
connection:willSendRequest:redirectResponse: delegate method (for NSURLDownload, implement
download:willSendRequest:redirectResponse:). If the delegate implements this method, it can
examine the new request and the response that caused the redirect, and respond in one of four ways:
● The delegate can allow the redirect by simply returning the provided request.
● The delegate can create a new request, pointing to a different URL, and return that request.
● The delegate can reject the redirect and receive any existing data from the connection by returning nil.
● The delegate can cancel both the redirect and the connection by sending the cancelmessage to the
NSURLConnection or NSURLDownload.
The delegate also receives the connection:willSendRequest:redirectResponse: message if the
NSURLProtocol subclass that handles the request has changed the NSURLRequest in order to standardize
its format, for example, changing a request for http://www.apple.com to http://www.apple.com/. This
occurs because the standardized, or canonical, version of the request is used for cache management. In this
special case, the response passed to the delegate is nil and the delegate should simply return the provided
request.
The example implementation in Listing 1 allows canonical changes and denies all server redirects.
Listing 1 Example of an implementation of connection:willSendRequest:redirectResponse:
-(NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
{
NSURLRequest *newRequest = request;
if (redirectResponse) {
newRequest = nil;
}
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
26
Handling Redirects and Other Request Changesreturn newRequest;
}
If the delegate doesn't implement connection:willSendRequest:redirectResponse:, all canonical
changes and server redirects are allowed.
Handling Redirects and Other Request Changes
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
27An NSURLRequest object often encounters an authentication challenge, or a request for credentials from the
server it is connecting to. The delegates for NSURLConnection and NSURLDownload can be notified when
their request encounters an authentication challenge, so that they can act accordingly.
Deciding How to Respond to an Authentication Challenge
If an NSURLRequest object requires authentication, the delegate of the NSURLConnection (or NSURLDownload)
object associated with the request first receives a
connection:canAuthenticateAgainstProtectionSpace: (or
download:canAuthenticateAgainstProtectionSpace:) message. This allows the delegate to analyze
properties of the server, including its protocol and authentication method, before attempting to authenticate
against it. If your delegate is not prepared to authenticate against the server’s protection space, you can return
NO, and the system attempts to authenticate with information from the user’s keychain.
Note: If your delegate does not implement the
connection:canAuthenticateAgainstProtectionSpace: method and the protection space
uses client certificate authentication or server trust authentication, the system behaves as if you
returned NO. The system behaves as if you returned YES for all other authentication methods.
If your delegate returns YES from connection:canAuthenticateAgainstProtectionSpace: or doesn’t
implement it, and there are no valid credentials available, either as part of the requested URL or in the shared
NSURLCredentialStorage,the delegate receives a connection:didReceiveAuthenticationChallenge:
message. In order for the connection to continue, the delegate has three options:
● Provide authentication credentials
● Attempt to continue without credentials
● Cancel the authentication challenge
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
28
Authentication ChallengesTo help determine the correct course of action, the NSURLAuthenticationChallenge instance passed to
the method contains information about what triggered the authentication challenge, how many attempts
were made for the challenge, any previously attempted credentials, the NSURLProtectionSpace that requires
the credentials, and the sender of the challenge.
If the authentication challenge has tried to authenticate previously and failed, you can obtain the attempted
credentials by calling proposedCredential on the authentication challenge. The delegate can then use
these credentials to populate a dialog that it presents to the user.
Calling previousFailureCount on the authentication challenge returns the total number of previous
authentication attempts, including those from different authentication protocols. The delegate can provide
this information to the end user, to determine whether the credentials it supplied previously are failing, or to
limit the maximum number of authentication attempts.
Responding to an Authentication Challenge
The following are the three ways you can respond to the
connection:didReceiveAuthenticationChallenge: delegate method.
Providing Credentials
To attempt to authenticate, the application should create an NSURLCredential object with authentication
information of the form expected by the server. You can determine the server’s authentication method by
calling authenticationMethod on the protection space of the provided authentication challenge. Some
authentication methods supported by NSURLCredential are:
HTTP Basic Authentication (NSURLAuthenticationMethodHTTPBasic)
The basic authentication method requires a user name and password. Prompt the user for the necessary
information and create an NSURLCredential object with
credentialWithUser:password:persistence:.
HTTP Digest Authentication (NSURLAuthenticationMethodHTTPDigest)
Like basic authentication, digest authentication just requires a user name and password (the digest is
generated automatically). Prompt the user for the necessary information and create an NSURLCredential
object with credentialWithUser:password:persistence:.
Client Certificate Authentication (NSURLAuthenticationMethodClientCertificate)
Client certificate authentication requires the system identity and all certificates needed to authenticate
with the server. Create an NSURLCredential object with
credentialWithIdentity:certificates:persistence:.
Authentication Challenges
Responding to an Authentication Challenge
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
29Server Trust Authentication (NSURLAuthenticationMethodServerTrust)
Server trust authentication requires a trust provided by the protection space of the authentication
challenge. Create an NSURLCredential object with credentialForTrust:.
After you’ve created the NSURLCredential object, pass it to the authentication challenge’s sender with
useCredential:forAuthenticationChallenge:.
Continuing Without Credentials
If the delegate chooses not to provide a credential for the authentication challenge, it can attempt to continue
without one by calling continueWithoutCredentialsForAuthenticationChallenge: on [challenge
sender]. Depending on the protocol implementation, continuing without credentials may either cause the
connection to fail, resulting in a connectionDidFailWithError: message, or return alternate URL contents
that don’t require authentication.
Canceling the Connection
The delegate may also choose to cancel the authentication challenge by calling
cancelAuthenticationChallenge: on [challenge sender]. The delegate receives a
connection:didCancelAuthenticationChallenge: message, providing the opportunity to give the
user feedback.
Para
The implementation shown in Listing 1 attempts to authenticate the challenge by creating an
NSURLCredential instance with a user name and password supplied by the application’s preferences. If the
authentication has failed previously, it cancels the authentication challenge and informs the user.
Listing 1 An example of using the connection:didReceiveAuthenticationChallenge: delegate method
-(void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge previousFailureCount] == 0) {
NSURLCredential *newCredential;
newCredential = [NSURLCredential credentialWithUser:[self preferencesName]
password:[self preferencesPassword]
persistence:NSURLCredentialPersistenceNone];
[[challenge sender] useCredential:newCredential
Authentication Challenges
Responding to an Authentication Challenge
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
30forAuthenticationChallenge:challenge];
} else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
// inform the user that the user name and password
// in the preferences are incorrect
[self showPreferencesCredentialsAreIncorrectPanel:self];
}
}
If the delegate doesn’t implement connection:didReceiveAuthenticationChallenge: and the request
requires authentication, valid credentials must already be available in the URL credential storage or must be
provided as part of the requested URL. If the credentials are not available or if they fail to authenticate, a
continueWithoutCredentialForAuthenticationChallenge: message is sent by the underlying
implementation.
Authentication Challenges
Responding to an Authentication Challenge
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
31The URL loading system provides a composite on-disk and in-memory cache of responses to requests. This
cache allows an application to reduce its dependency on a network connection and increase its performance.
Using the Cache for a Request
An NSURLRequest instance specifies how the local cache is used by setting the cache policy to one of the
NSURLRequestCachePolicy values: NSURLRequestUseProtocolCachePolicy,
NSURLRequestReloadIgnoringCacheData, NSURLRequestReturnCacheDataElseLoad, or
NSURLRequestReturnCacheDataDontLoad.
The default cache policy for an NSURLRequest instance is NSURLRequestUseProtocolCachePolicy. The
NSURLRequestUseProtocolCachePolicy behavior is protocol specific and is defined as being the best
conforming policy for the protocol.
Setting the cache policy to NSURLRequestReloadIgnoringCacheData causes the URL loading system to
load the data from the originating source, ignoring the cache completely.
The NSURLRequestReturnCacheDataElseLoad cache policy will cause the URL loading system to use
cached data ignoring its age or expiration date, if it exists, and load the data from the originating source only
if there is no cached version.
The NSURLRequestReturnCacheDataDontLoad policy allows an application to specify that only data in the
cache should be returned. Attempting to create an NSURLConnection or NSURLDownload instance with this
cache policy returns nil immediately if the response is not in the local cache. This is similar in function to an
“offline” mode and never brings up a network connection.
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
32
Understanding Cache AccessNote: Currently, only responsesto http and https requests are cached. The ftp and file protocols
attempt to access the originating source as allowed by the cache policy. Custom NSURLProtocol
classes can provide caching if they choose.
Cache Use Semantics for the http Protocol
The most complicated cache use situation is when a request uses the http protocol and has set the cache
policy to NSURLRequestUseProtocolCachePolicy.
If an NSCachedURLResponse does not exist for the request, then the data isfetched from the originating source.
If there is a cached response for the request, the URL loading system checks the response to determine if it
specifies that the contents must be revalidated. If the contents must be revalidated a connection is made to
the originating source to see if it has changed. If it has not changed, then the response is returned from the
local cache. If it has changed, the data is fetched from the originating source.
If the cached response doesn’t specify that the contents must be revalidated, the maximum age or expiration
specified in the response is examined. If the cached response is recent enough, then the response is returned
from the local cache. If the response is determined to be stale, the originating source is checked for newer
data. If newer data is available, the data is fetched from the originating source, otherwise it is returned from
the cache.
RFC 2616, Section 13 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13) specifies the semantics
involved in detail.
Understanding Cache Access
Cache Use Semantics for the http Protocol
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
33This table describes the changes to URL Loading System Programming Guide .
Date Notes
2010-09-01 Fixed typos and removed deprecated symbols from code examples.
Restructured content and added discussions of new authentication
functionality.
2010-03-24
2009-08-14 Added links to Cocoa Core Competencies.
2008-05-20 Updated to include content about NSURLDownload availability in iOS.
2008-05-06 Made minor editorial changes.
2007-07-10 Corrected minor typos.
2006-05-23 Added links to sample code.
2006-03-08 Updated sample code.
2005-09-08 Corrected connectionDidFinishLoading: method signature.
2005-04-08 Added accessor method to sample code. Corrected minor typos.
2004-08-31 Corrected minor typos.
Corrected table of contents ordering.
Corrected willSendRequest:redirectResponse: method signature
throughout topic.
2003-07-03
Added additional article outlining differences in behavior between
NSURLDownload and NSURLConnection.
2003-06-11
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
34
Document Revision HistoryDate Notes
First release of conceptual and task material covering the usage of new
classes in Mac OS X v10.2 with Safari 1.0 for downloading content from
the Internet.
2003-06-06
Document Revision History
2010-09-01 | © 2003, 2010 Apple Inc. All Rights Reserved.
35Apple Inc.
© 2003, 2010 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Cocoa, Mac, Mac OS, OS
X, and Safari are trademarks of Apple Inc.,
registered in the U.S. and other countries.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
External Accessory
Programming TopicsContents
About External Accessories 4
At a Glance 4
Including the External Accessory Framework in Your Project 4
Declaring the Protocols Your App Supports 5
Communicating with an Accessory 5
See Also 5
Connecting to an Accessory 6
Monitoring Accessory-Related Events 9
Document Revision History 10
2012-02-24 | © 2012 Apple Inc. All Rights Reserved.
2Listings
Connecting to an Accessory 6
Listing 1 Creating a communications session for an accessory 6
Listing 2 Processing stream events 8
2012-02-24 | © 2012 Apple Inc. All Rights Reserved.
3The External Accessory framework (ExternalAccessory.framework) provides a conduit for communicating
with accessories attached to any iOS-based device. App developers can use this conduit to integrate
accessory-level features into their apps.
Communicating with an external accessory requires you to work closely with the accessory manufacturer to
understand the services provided by that accessory. Manufacturers must build explicit support into their
accessory hardware for communicating with iOS. As part of this support, an accessory must support at least
one command protocol, which is a custom scheme for sending data back and forth between the accessory
and an attached app. Apple does not maintain a registry of protocols; it is up to the manufacturer to decide
which protocols to support and whether to use custom protocols or standard protocols supported by other
manufacturers.
As part of your communication with the accessory manufacturer, you must find out what protocols a given
accessory supports. To prevent namespace conflicts, protocol names are specified as reverse-DNS strings of
the form com.apple.myProtocol. This allows each manufacturer to define as many protocols as needed to
support their line of accessories.
Note: If you are interested in becoming a developer of accessories for iPad, iPhone, or iPod touch,
you can find information about how to do so on http://developer.apple.com.
At a Glance
Communicating with accessories requires information about the accessory itself, which you must obtain from
the hardware manufacturer. From there, you use the classes of the External Accessory framework to create the
bridge between the hardware and your app.
Including the External Accessory Framework in Your Project
To use the features of the External Accessory framework, you must add ExternalAccessory.framework
to your Xcode project and link against it in any relevant targets. To access the classes and headers of the
framework, include an #import statement at the top of
any relevant source files.
2012-02-24 | © 2012 Apple Inc. All Rights Reserved.
4
About External AccessoriesDeclaring the Protocols Your App Supports
Appsthat are able to communicate with an external accessory must declare the protocolsthey support in their
Info.plist file. Declaring support for specific protocols lets the system know that your app can be launched
when that accessory is connected. If no app supports the connected accessory, the system may choose to
launch the App Store and point out apps that do.
To declare the protocols your app supports, you must include the
UISupportedExternalAccessoryProtocols key in your app’s Info.plist file. This key contains an array
ofstringsthat identify the communications protocolsthat your app supports. Your app can include any number
of protocols in this list and the protocols can be in any order. The system does not use this list to determine
which protocol your app should choose; it uses it only to determine if your app is capable of communicating
with the accessory. It is up to your code to choose an appropriate communications protocol when it begins
talking to the accessory.
For more information about the keys you put into your app’s Info.plist file, see Information Property List
Key Reference .
Communicating with an Accessory
An app communicates with an accessory by creating an EASession object for managing the accessory
interactions. Session objects work with the underlying system to transfer data packetsto and from the accessory.
Data transfer in your app occurs through NSInputStream and NSOutputStream objects, which are vended
by the session object once the connection is made. To receive data from the accessory, monitor the input
stream using a custom delegate object. To send data to the accessory, write data packets to the output stream.
The format of the incoming and outgoing data packetsis determined by the protocol you use to communicate
with the accessory.
Relevant Article: “Connecting to an Accessory” (page 6), “Monitoring Accessory-Related
Events” (page 9)
See Also
For information about the classes of the External Accessory framework, see External Accessory Framework
Reference
About External Accessories
See Also
2012-02-24 | © 2012 Apple Inc. All Rights Reserved.
5Accessories are not visible through the External Accessory framework until they have been connected by the
system and made ready for use. When an accessory does become visible, your app can get the appropriate
accessory object and open a session using one or more of the protocols supported by the accessory.
The shared EAAccessoryManager object provides the main entry point for apps looking to communicate
with accessories. This class contains an array of already connected accessory objects that you can enumerate
to see if there is one your app supports. Most of the information in an EAAccessory object (such asthe name,
manufacturer, and model information) is intended for display purposes only. To determine whether your app
can connect to an accessory, you must look at the accessory’s protocols and see if there is one your app
supports.
Note: It is possible for more than one accessory object to support the same protocol. If that happens,
your code is responsible for choosing which accessory object to use.
For a given accessory object, only one session at a time is allowed for a specific protocol. The protocolStrings
property of each EAAccessory object contains a dictionary whose keys are the supported protocols. If you
attempt to create a session using a protocol that is already in use, the External Accessory framework closes
the existing session before opening the new one.
Listing 1 shows a method that checks the list of connected accessories and grabs the first one that the app
supports. It creates a session for the designated protocol and configures the input and output streams of the
session. By the time this method returnsthe session object, it is connected to the accessory and ready to begin
sending and receiving data.
Listing 1 Creating a communications session for an accessory
- (EASession *)openSessionForProtocol:(NSString *)protocolString
{
NSArray *accessories = [[EAAccessoryManager sharedAccessoryManager]
connectedAccessories];
EAAccessory *accessory = nil;
EASession *session = nil;
2012-02-24 | © 2012 Apple Inc. All Rights Reserved.
6
Connecting to an Accessoryfor (EAAccessory *obj in accessories)
{
if ([[obj protocolStrings] containsObject:protocolString])
{
accessory = obj;
break;
}
}
if (accessory)
{
session = [[EASession alloc] initWithAccessory:accessory
forProtocol:protocolString];
if (session)
{
[[session inputStream] setDelegate:self];
[[session inputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[[session inputStream] open];
[[session outputStream] setDelegate:self];
[[session outputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[[session outputStream] open];
[session autorelease];
}
}
return session;
}
After the input and output streams are configured, the final step is to process the stream-related data. Listing
2 shows the fundamental structure of a delegate’s stream processing code. This method responds to events
from both input and output streams of the accessory. As the accessory sends data to your app an event arrives
indicating there are bytes available to be read. Similarly, when the accessory is ready to receive data from your
app, events arrive indicating that fact. (Of course, your app does not always have to wait for an event to arrive
Connecting to an Accessory
2012-02-24 | © 2012 Apple Inc. All Rights Reserved.
7before it can write bytes to the stream. It can also call the stream’s hasBytesAvailable method to see if the
accessory is still able to receive data.) For more information on streams and handling stream-related events,
see Stream Programming Guide .
Listing 2 Processing stream events
// Handle communications from the streams.
- (void)stream:(NSStream*)theStream handleEvent:(NSStreamEvent)streamEvent
{
switch (streamEvent)
{
case NSStreamHasBytesAvailable:
// Process the incoming stream data.
break;
case NSStreamEventHasSpaceAvailable:
// Send the next queued command.
break;
default:
break;
}
}
Connecting to an Accessory
2012-02-24 | © 2012 Apple Inc. All Rights Reserved.
8The External Accessory framework is capable of sending notifications whenever a hardware accessory is
connected or disconnected. Although it is capable, it does not do so automatically. Your app must specifically
request that notifications be generated by calling the registerForLocalNotifications method of the
EAAccessoryManager class. When an accessory is connected, authenticated, and ready to interact with your
app, the framework sends an EAAccessoryDidConnectNotification notification. When an accessory is
disconnected, it sends an EAAccessoryDidDisconnectNotification notification. You can register to
receive these notifications using the default NSNotificationCenter, and both notifications include
information about which accessory was affected.
In addition to receiving notificationsthrough the default notification center, an app that is currently interacting
with an accessory can assign a delegate to the corresponding EAAccessory object and be notified of changes.
Delegate objects must conform to the EAAccessoryDelegateprotocol, which currently contains the optional
accessoryDidDisconnect: method. You can use this method to receive disconnection notices without
first setting up a notification observer.
If your app is suspended in the background when an accessory notification arrives, that notification is put in
a queue. When your app begins running again (either in the foreground or background), notifications in the
queue are delivered to your app. Notifications are also coalesced and filtered wherever possible to eliminate
any irrelevant events. For example, if an accessory was connected and subsequently disconnected while your
app was suspended, your app would ultimately not receive any indication that such events took place.
For more information about how to register to receive notifications, see Notification Programming Topics.
2012-02-24 | © 2012 Apple Inc. All Rights Reserved.
9
Monitoring Accessory-Related EventsThis table describes the changes to External Accessory Programming Topics.
Date Notes
2012-02-24 Clarified that protocols must be declared in an app's Info.plist file.
Corrected information about what happens when you connect to an
existing session.
2011-09-22
2010-05-26 New document describing how to attach to external hardware devices.
2012-02-24 | © 2012 Apple Inc. All Rights Reserved.
10
Document Revision HistoryApple Inc.
© 2012 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, iPad, iPhone, iPod, iPod
touch, and Xcode are trademarks of Apple Inc.,
registered in the U.S. and other countries.
App Store is a service mark of Apple Inc.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
Multimedia
Programming GuideContents
About Audio and Video 4
Organization of This Document 4
Using Audio 5
The Basics: Audio Codecs, Supported Audio Formats, and Audio Sessions 6
iOS Hardware and Software Audio Codecs 7
Audio Sessions 9
Playing Audio 11
Playing Audio Items with iPod Library Access 12
Playing UI Sound Effects or Invoking Vibration Using System Sound Services 12
Playing Sounds Easily with the AVAudioPlayer Class 15
Playing Sounds with Control Using Audio Queue Services 17
Playing Sounds with Positioning Using OpenAL 21
Recording Audio 21
Recording with the AVAudioRecorder Class 22
Recording with Audio Queue Services 24
Parsing Streamed Audio 25
Audio Unit Support in iOS 26
Best Practices for iOS Audio 27
Tips for Using Audio 27
Preferred Audio Formats in iOS 28
Using Video 30
Recording and Editing Video 30
Playing Video Files 31
Document Revision History 34
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
2Figures, Tables, and Listings
Using Audio 5
Figure 1-1 Using iPod library access 12
Table 1-1 Audio playback formats and codecs 7
Table 1-2 Audio recording formats and codecs 8
Table 1-3 Features provided by the audio session APIs 9
Table 1-4 Handling audio interruptions 11
Table 1-5 System-supplied audio units 26
Table 1-6 Audio tips 27
Listing 1-1 Creating a sound ID object 13
Listing 1-2 Playing a system sound 14
Listing 1-3 Triggering vibration 14
Listing 1-4 Configuring an AVAudioPlayer object 15
Listing 1-5 Implementing an AVAudioPlayer delegate method 16
Listing 1-6 Controlling an AVAudioPlayer object 17
Listing 1-7 Creating an audio queue object 18
Listing 1-8 Setting the playback level directly 20
Listing 1-9 The AudioQueueLevelMeterState structure 21
Listing 1-10 Setting up the audio session and the sound file URL 22
Listing 1-11 A record/stop method using the AVAudioRecorder class 23
Using Video 30
Figure 2-1 Media player interface with transport controls 31
Listing 2-1 Playing full-screen movies 31
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
3Whether multimedia features are central or incidental to your application, iPhone, iPod touch, and iPad users
expect high quality. When presenting video content, take advantage of the device’s high-resolution screen
and high frame rates. When designing the audio portion of your application, keep in mind that compelling
sound adds immeasurably to a user’s overall experience.
You can take advantage of the iOS multimedia frameworks for adding features like:
● High-quality audio recording, playback, and streaming
●
Immersive game sounds
● Live voice chat
● Playback of content from a user’s iPod library
● Video playback and recording on supported devices
In iOS 4.0 and later, the AV Foundation framework gives you fine-grained control over inspecting, editing, and
presenting audio-visual assets.
Organization of This Document
This document contains the following chapters:
●
“Using Audio” (page 5) shows how to use the system’s audio technologies to play and record audio.
●
“Using Video” (page 30) shows how to use the system’s video technologies to play and capture video.
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
4
About Audio and VideoImportant: This document contains information that used to be in iOS App Programming Guide . The
information in this document has not been updated specifically for iOS 4.0.
iOS offers a rich set of toolsfor working with sound in your application. These tools are arranged into frameworks
according to the features they provide, as follows:
● Use the Media Player framework to play songs, audio books, or audio podcasts from a user’s iPod library.
For details, see Media Player Framework Reference , iPod Library Access Programming Guide , and the
AddMusic sample code project.
● Use the AV Foundation framework to play and record audio using a simple Objective-C interface. For
details, see AV Foundation Framework Reference and the avTouch sample code project.
● Use the Audio Toolbox framework to play audio with synchronization capabilities, access packets of
incoming audio, parse audio streams, convert audio formats, and record audio with access to individual
packets. For details, see Audio Toolbox Framework Reference and the SpeakHere sample code project.
● Use the Audio Unit framework to connect to and use audio processing plug-ins. For details, see Audio
Unit Hosting Guide for iOS .
● Use the OpenAL framework to provide positional audio playback in games and other applications. iOS
supports OpenAL 1.1. For information on OpenAL, see the OpenAL website, OpenAL FAQ for iPhone OS ,
and the oalTouch sample code project.
To allow your code to use the features of an audio framework, add that framework to your Xcode project, link
against it in any relevant targets, and add an appropriate #import statement near the top of relevant source
files. For example, to provide access to the AV Foundation framework in a source file, add a #import
statement near the top of the file. For detailed information on how to
add frameworks to your project, see “Files in Projects” in Xcode Project Management Guide .
Important: To use the features of the Audio Unit framework, add the Audio Toolbox framework to your
Xcode project and link against it in any relevant targets. Then add a #import
statement near the top of relevant source files.
This section on sound provides a quick introduction to implementing iOS audio features, as listed here:
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
5
Using Audio● To play songs, audio podcasts, and audio books from a user’s iPod library, see “Playing Audio Items with
iPod Library Access” (page 12).
● To play and record audio in the fewest lines of code, use the AV Foundation framework. See “Playing
Sounds Easily with the AVAudioPlayer Class” (page 15) and “Recording with the AVAudioRecorder
Class” (page 22).
● To provide full-featured audio playback including stereo positioning, level control, and simultaneous
sounds, use OpenAL. See “Playing Sounds with Positioning Using OpenAL” (page 21).
● To provide lowest latency audio, especially when doing simultaneous input and output (such as for a VoIP
application), use the I/O unit or the Voice Processing I/O unit. See “Audio Unit Support in iOS” (page 26).
● To play sounds with the highest degree of control, including support forsynchronization, use Audio Queue
Services. See “Playing Sounds with Control Using Audio Queue Services” (page 17). Audio Queue Services
also supports recording and provides access to incoming audio packets, as described in “Recording with
Audio Queue Services” (page 24).
● To parse audio streamed from a network connection, use Audio File Stream Services. See “Parsing Streamed
Audio” (page 25).
● To play user-interface sound effects, or to invoke vibration on devicesthat provide that feature, use System
Sound Services. See “Playing UI Sound Effects or Invoking Vibration Using System Sound Services” (page
12).
Be sure to read the nextsection,“The Basics: Audio Codecs, Supported Audio Formats, and Audio Sessions” (page
6), for critical information on how audio works in iOS. Also read “Best Practices for iOS Audio” (page 27),
which offers guidelines and liststhe audio and file formatsto use for best performance and best user experience.
When you’re ready to dig deeper, the iOS Dev Center contains guides, reference books, sample code, and
more. For tips on how to perform common audio tasks, see Audio & Video Coding How-To's. For in-depth
explanations of audio development in iOS, see Core Audio Overview, Audio Session Programming Guide , Audio
Queue Services Programming Guide , Audio Unit Hosting Guide for iOS , and iPod Library Access Programming
Guide .
The Basics: Audio Codecs, Supported Audio Formats, and Audio
Sessions
To get oriented toward iOS audio development, it’s important to understand a few critical things about the
hardware and software architecture of iOS devices—described in this section.
Using Audio
The Basics: Audio Codecs, Supported Audio Formats, and Audio Sessions
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
6iOS Hardware and Software Audio Codecs
To ensure optimum performance and quality, you need to pick the right audio format and audio codec type.
Starting in iOS 3.0, most audio formats can use software-based encoding (for recording) and decoding (for
playback). Software codecs support simultaneous playback of multiple sounds, but may entail significant CPU
overhead.
Hardware-assisted decoding provides excellent performance—but does not support simultaneous playback
of multiple sounds. If you need to maximize video frame rate in your application, minimize the CPU impact of
your audio playback by using uncompressed audio or the IMA4 format, or use hardware-assisted decoding of
your compressed audio assets.
For best-practice advice on picking an audio format, see “Preferred Audio Formats in iOS” (page 28).
Table 1-1 describes the playback audio codecs available on iOS devices.
Table 1-1 Audio playback formats and codecs
Hardware-assisted Software-based decoding
decoding
Audio decoder/playback format
AAC (MPEG-4 Advanced Audio Coding) Yes Yes, starting in iOS 3.0
ALAC (Apple Lossless) Yes Yes, starting in iOS 3.0
HE-AAC (MPEG-4 High Efficiency AAC) Yes -
iLBC (internet Low Bitrate Codec, another format - Yes
for speech)
IMA4 (IMA/ADPCM) - Yes
Linear PCM (uncompressed, linear pulse-code - Yes
modulation)
MP3 (MPEG-1 audio layer 3) Yes Yes, starting in iOS 3.0
µ-law and a-law - Yes
When using hardware-assisted decoding, the device can play only a single instance of one of the supported
formats at a time. For example, if you are playing a stereo MP3 sound using the hardware codec, a second
simultaneous MP3 sound will use software decoding. Similarly, you cannot simultaneously play an AAC and
an ALAC sound using hardware. If the iPod application is playing an AAC or MP3 sound in the background, it
has claimed the hardware codec; your application then plays AAC, ALAC, and MP3 audio using software
decoding.
Using Audio
The Basics: Audio Codecs, Supported Audio Formats, and Audio Sessions
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
7To play multiple sounds with best performance, or to efficiently play sounds while the iPod is playing in the
background, use linear PCM (uncompressed) or IMA4 (compressed) audio.
To learn how to check at runtime which hardware and software codecs are available on a device, read the
discussion for the kAudioFormatProperty_HardwareCodecCapabilities constant in Audio Format
Services Reference and read Technical Q&A QA1663, “Determining the availability of the AAC hardware encoder
at runtime.”
To summarize how iOS supports audio formats for single or multiple playback:
● Linear PCM and IMA4 (IMA/ADPCM) You can play multiple linear PCM or IMA4 sounds simultaneously
in iOS without incurring CPU resource problems. The same is true for the iLBC speech-quality format, and
for the µ-law and a-law compressed formats. When using compressed formats, check the sound quality
to ensure it meets your needs.
● AAC, HE-AAC, MP3, and ALAC (Apple Lossless) Playback for AAC, HE-AAC, MP3, and ALAC sounds can
use efficient hardware-assisted decoding on iOS devices, but these codecs all share a single hardware
path. The device can play only a single instance of one of these formats at a time using hardware-assisted
decoding.
The single hardware path for AAC, HE-AAC, MP3, and ALAC playback has implications for “play along” style
applications, such as a virtual piano. If the user is playing a song in one of these three formats in the iPod
application, then your application—to play along over that audio—will employ software decoding.
Table 1-2 describes the recording audio codecs available on iOS devices.
Table 1-2 Audio recording formats and codecs
Audio encoder/recording format Hardware-assisted encoding Software-based encoding
Yes, starting in iOS 4.0 for
iPhone 3GS and iPod
touch (2nd generation)
Yes, starting in iOS 3.1 for
iPhone 3GS and iPod touch
(2nd generation)
Yes, starting in iOS 3.2 for
iPad
AAC (MPEG-4 Advanced Audio Coding)
ALAC (Apple Lossless) - Yes
iLBC (internet Low Bitrate Codec, for - Yes
speech)
IMA4 (IMA/ADPCM) - Yes
Linear PCM (uncompressed, linear - Yes
pulse-code modulation)
Using Audio
The Basics: Audio Codecs, Supported Audio Formats, and Audio Sessions
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
8Audio encoder/recording format Hardware-assisted encoding Software-based encoding
µ-law and a-law - Yes
Audio Sessions
The iOS audio session APIs let you define your application’s general audio behavior and design it to work well
within the larger audio context of the device it’s running on. These APIs are described in Audio Session Services
Reference and AVAudioSession Class Reference . Using these APIs, you can specify such behaviors as:
● Whether or not your audio should be silenced by the Silent switch (on iPhone, this is called the Ring/Silent
switch )
● Whether or not your audio should stop upon screen lock
● Whether other audio, such as from the iPod, should continue playing or be silenced when your audio
starts
The audio session APIs also let you respond to user actions, such as the plugging in or unplugging of headsets,
and to events that use the device’s sound hardware, such as Clock and Calendar alarms and incoming phone
calls.
The audio session APIs provide three programmatic features, described in Table 1-3.
Table 1-3 Features provided by the audio session APIs
Audio session feature Description
A category is a key that identifies a set of audio behaviorsfor your application.
By setting a category, you indicate your audio intentions to iOS, such as
whether your audio should continue when the screen locks. There are six
categories, described in “Audio Session Categories”. You can fine-tune the
behavior of some categories, as explained in “Fine-Tuning the Category” in
Audio Session Programming Guide .
Setting categories
Your audio session posts messages when your audio is interrupted, when an
interruption ends, and when the hardware audio route changes. These
messages let you respond gracefully to changes in the larger audio
environment—such as an interruption due to an incoming phone call. For
details, see “Handling Audio Hardware Route Changes” and “Handling Audio
Interruptions”.
Handling interruptions
and route changes
You can query the audio session to discover characteristics of the device your
application isrunning on,such as hardware sample rate, number of hardware
channels, and whether audio input is available. For details, see “Optimizing
for Device Hardware”.
Optimizing for
hardware
characteristics
Using Audio
The Basics: Audio Codecs, Supported Audio Formats, and Audio Sessions
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
9There are two interfaces for working with the audio session:
● A streamlined, objective-C interface that gives you accessto the core audio session features and is described
in AVAudioSession Class Reference and AVAudioSessionDelegate Protocol Reference .
● A C-based interface that provides comprehensive access to all basic and advanced audio session features
and is described in Audio Session Services Reference .
You can mix and match audio session code from AV Foundation and Audio Session Services—the interfaces
are completely compatible.
An audio session comes with some default behavior that you can use to get started in development. However,
except for certain special cases, the default behavior is unsuitable for a shipping application that uses audio.
For example, when using the default audio session, audio in your application stops when the Auto-Lock period
times out and the screen locks. If you want to ensure that playback continues with the screen locked, include
the following lines in your application’s initialization code:
NSError *setCategoryErr = nil;
NSError *activationErr = nil;
[[AVAudioSession sharedInstance]
setCategory: AVAudioSessionCategoryPlayback
error: &setCategoryErr];
[[AVAudioSession sharedInstance]
setActive: YES
error: &activationErr];
The AVAudioSessionCategoryPlayback category ensures that playback continues when the screen locks.
Activating the audio session puts the specified category into effect.
How you handle the interruption caused by an incoming phone call or Clock or Calendar alarm depends on
the audio technology you are using, as shown in Table 1-4.
Using Audio
The Basics: Audio Codecs, Supported Audio Formats, and Audio Sessions
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
10Table 1-4 Handling audio interruptions
Audio technology How interruptions work
The AVAudioPlayer and AVAudioRecorder classes provide delegate
methods for interruption start and end. Implement these methods to
update your user interface and optionally, after interruption ends, to
resume paused playback. The system automatically pauses playback or
recording upon interruption, and reactivates your audio session when
you resume playback or recording.
If you want to save and restore playback position between application
launches,save playback position on interruption as well as on application
quit.
AV Foundation framework
These technologies put your application in control of handling
interruptions. You are responsible for saving playback or recording
position and reactivating your audio session after interruption ends.
Implement the AVAudioSession interruption delegate methods or
write an interruption listener callback function.
Audio Queue Services,
I/O audio unit
When using OpenAL for playback, implement the AVAudioSession
interruption delegate methods or write an interruption listener callback
function—as when using Audio Queue Services. However, the delegate
or callback must additionally manage the OpenAL context.
OpenAL
Sounds played using System Sound Services go silent when an
interruption starts. They can automatically be used again if the
interruption ends. Applications cannotinfluence the interruption behavior
for sounds that use this playback technology.
System Sound Services
Every iOS application—with rare exception—should actively manage its audio session. For a complete
explanation of how to do this,read Audio Session ProgrammingGuide . To ensure that your application conforms
to Apple recommendations for audio session behavior, read “Sound” in iOS Human Interface Guidelinesin iOS
Human Interface Guidelines.
Playing Audio
This section introduces you to playing sounds in iOS using iPod library access, System Sound Services, Audio
Queue Services, the AV Foundation framework, and OpenAL.
Using Audio
Playing Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
11Playing Audio Items with iPod Library Access
Starting in iOS 3.0, iPod library accesslets your application play a user’ssongs, audio books, and audio podcasts.
The API design makes basic playback very simple while also supporting advanced searching and playback
control.
As shown in Figure 1-1, your application has two ways to retrieve media items. The media item picker, shown
on the left, is an easy-to-use, pre-packaged view controller that behaves like the built-in iPod application’s
music selection interface. For many applications, this is sufficient. If the picker doesn’t provide the specialized
access control you want, the media query interface will. Itsupports predicate-based specification of itemsfrom
the iPod library.
Figure 1-1 Using iPod library access
iPod Library
Music Player
Media Picker Media Query
Your application
As depicted in the figure to the right of your application, you then play the retrieved media items using the
music player provided by this API.
For a complete explanation of how to add media item playback to your application, see iPod Library Access
Programming Guide . For a code example, see the AddMusic sample code project.
Playing UI Sound Effects or Invoking Vibration Using System Sound Services
To play user-interface sound effects (such as button clicks), or to invoke vibration on devices that support it,
use System Sound Services. This compact interface is described in System Sound Services Reference . You can
find sample code in the Audio UI Sounds (SysSound) sample in the iOS Dev Center.
Using Audio
Playing Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
12Note: Sounds played with System Sound Services are not subject to configuration using your audio
session. As a result, you cannot keep the behavior of System Sound Services audio in line with other
audio behavior in your application. This is the most important reason to avoid using System Sound
Services for any audio apart from its intended uses.
The AudioServicesPlaySystemSound function lets you very simply play short sound files. The simplicity
carries with it a few restrictions. Your sound files must be:
● No longer than 30 seconds in duration
●
In linear PCM or IMA4 (IMA/ADPCM) format
● Packaged in a .caf, .aif, or .wav file
In addition, when you use the AudioServicesPlaySystemSound function:
● Sounds play at the current system audio volume, with no programmatic volume control available
● Sounds play immediately
● Looping and stereo positioning are unavailable
● Simultaneous playback is unavailable: You can play only one sound at a time
The similar AudioServicesPlayAlertSound function plays a shortsound as an alert. If a user has configured
their device to vibrate in Ring Settings, calling this function invokes vibration in addition to playing the sound
file.
Note: System-supplied alert sounds and system-supplied user-interface sound effects are not
available to your application. For example, using the kSystemSoundID_UserPreferredAlert
constant as a parameter to the AudioServicesPlayAlertSound function will not play anything.
To play a sound with the AudioServicesPlaySystemSound or AudioServicesPlayAlertSound function,
first create a sound ID object, as shown in Listing 1-1.
Listing 1-1 Creating a sound ID object
// Get the main bundle for the app
CFBundleRef mainBundle = CFBundleGetMainBundle ();
// Get the URL to the sound file to play. The file in this case
// is "tap.aif"
Using Audio
Playing Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
13soundFileURLRef = CFBundleCopyResourceURL (
mainBundle,
CFSTR ("tap"),
CFSTR ("aif"),
NULL
);
// Create a system sound object representing the sound file
AudioServicesCreateSystemSoundID (
soundFileURLRef,
&soundFileObject
);
Then play the sound, as shown in Listing 1-2.
Listing 1-2 Playing a system sound
- (IBAction) playSystemSound {
AudioServicesPlaySystemSound (self.soundFileObject);
}
In typical use, which includes playing a sound occasionally or repeatedly, retain the sound ID object until your
application quits. If you know that you will use a sound only once—for example, in the case of a startup
sound—you can destroy the sound ID object immediately after playing the sound, freeing memory.
Applicationsrunning on iOS devicesthatsupport vibration can trigger that feature using System Sound Services.
You specify the vibrate option with the kSystemSoundID_Vibrate identifier. To trigger it, use the
AudioServicesPlaySystemSound function, as shown in Listing 1-3.
Listing 1-3 Triggering vibration
#import
#import
- (void) vibratePhone {
AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);
}
Using Audio
Playing Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
14If your application is running on an iPod touch, this code does nothing.
Playing Sounds Easily with the AVAudioPlayer Class
The AVAudioPlayer class provides a simple Objective-C interface for playing sounds. If your application does
not require stereo positioning or precise synchronization, and if you are not playing audio captured from a
network stream, Apple recommends that you use this class for playback.
Using an audio player you can:
● Play sounds of any duration
● Play sounds from files or memory buffers
● Loop sounds
● Play multiple sounds simultaneously (although not with precise synchronization)
● Control relative playback level for each sound you are playing
● Seek to a particular point in a sound file, which supports application features such as fast forward and
rewind
● Obtain audio power data that you can use for audio level metering
The AVAudioPlayer class lets you play sound in any audio format available in iOS, as described in Table
1-1 (page 7). For a complete description of this class’s interface, see AVAudioPlayer Class Reference .
To configure an audio player:
1. Assign a sound file to the audio player.
2. Prepare the audio player for playback, which acquires the hardware resources it needs.
3. Designate an audio player delegate object, which handlesinterruptions as well asthe playback-completed
event.
The code in Listing 1-4 illustratesthese steps. It would typically go into an initialization method of the controller
class for your application. (In production code, you’d include appropriate error handling.)
Listing 1-4 Configuring an AVAudioPlayer object
// in the corresponding .h file:
// @property (nonatomic, retain) AVAudioPlayer *player;
// in the .m file:
@synthesize player; // the player object
Using Audio
Playing Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
15NSString *soundFilePath =
[[NSBundle mainBundle] pathForResource: @"sound"
ofType: @"wav"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
AVAudioPlayer *newPlayer =
[[AVAudioPlayer alloc] initWithContentsOfURL: fileURL
error: nil];
[fileURL release];
self.player = newPlayer;
[newPlayer release];
[player prepareToPlay];
[player setDelegate: self];
The delegate (which can be your controller object) handles interruptions and updates the user interface when
a sound has finished playing. The delegate methods for the AVAudioPlayer class are described in
AVAudioPlayerDelegate Protocol Reference . Listing 1-5 shows a simple implementation of one delegatemethod.
This code updates the title of a Play/Pause toggle button when a sound has finished playing.
Listing 1-5 Implementing an AVAudioPlayer delegate method
- (void) audioPlayerDidFinishPlaying: (AVAudioPlayer *) player
successfully: (BOOL) completed {
if (completed == YES) {
[self.button setTitle: @"Play" forState: UIControlStateNormal];
}
}
To play, pause, or stop an AVAudioPlayer object, call one of its playback control methods. You can test
whether or not playback is in progress by using the playing property. Listing 1-6 shows a basic play/pause
toggle method that controls playback and updates the title of a UIButton object.
Using Audio
Playing Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
16Listing 1-6 Controlling an AVAudioPlayer object
- (IBAction) playOrPause: (id) sender {
// if already playing, then pause
if (self.player.playing) {
[self.button setTitle: @"Play" forState: UIControlStateHighlighted];
[self.button setTitle: @"Play" forState: UIControlStateNormal];
[self.player pause];
// if stopped or paused, start playing
} else {
[self.button setTitle: @"Pause" forState: UIControlStateHighlighted];
[self.button setTitle: @"Pause" forState: UIControlStateNormal];
[self.player play];
}
}
The AVAudioPlayer class uses the Objective-C declared properties feature for managing information about a
sound—such as the playback point within the sound’s timeline, and for accessing playback options—such as
volume and looping. For example, you can set the playback volume for an audio player as shown here:
[self.player setVolume: 1.0]; // available range is 0.0 through 1.0
For more information on the AVAudioPlayer class, see AVAudioPlayer Class Reference .
Playing Sounds with Control Using Audio Queue Services
Audio Queue Services adds playback capabilities beyond those available with the AVAudioPlayer class. Using
Audio Queue Services for playback lets you:
● Precisely schedule when a sound plays, allowing synchronization
● Precisely control volume on a buffer-by-buffer basis
● Play audio that you have captured from a stream using Audio File Stream Services
Audio Queue Services lets you play sound in any audio format available in iOS, as described in Table 1-1 (page
7). You can also use this technology for recording, as explained in “Recording Audio” (page 21).
Using Audio
Playing Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
17For detailed information on using this technology, see Audio Queue Services Programming Guide and Audio
Queue Services Reference . For sample code, see the SpeakHere sample.
Creating an Audio Queue Object
To create an audio queue object for playback, perform these three steps:
1. Create a data structure to manage information needed by the audio queue, such as the audio format for
the data you want to play.
2. Define a callback function for managing audio queue buffers. The callback uses Audio File Servicesto read
the file you want to play. (In iOS 2.1 and later, you can also use Extended Audio File Services to read the
file.)
3. Instantiate the playback audio queue using the AudioQueueNewOutput function.
Listing 1-7 illustrates these steps using ANSI C. (In production code, you’d include appropriate error handling.)
The SpeakHere sample project shows these same steps in the context of a C++ program.
Listing 1-7 Creating an audio queue object
static const int kNumberBuffers = 3;
// Create a data structure to manage information needed by the audio queue
struct myAQStruct {
AudioFileID mAudioFile;
CAStreamBasicDescription mDataFormat;
AudioQueueRef mQueue;
AudioQueueBufferRef mBuffers[kNumberBuffers];
SInt64 mCurrentPacket;
UInt32 mNumPacketsToRead;
AudioStreamPacketDescription *mPacketDescs;
bool mDone;
};
// Define a playback audio queue callback function
static void AQTestBufferCallback(
void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inCompleteAQBuffer
) {
Using Audio
Playing Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
18myAQStruct *myInfo = (myAQStruct *)inUserData;
if (myInfo->mDone) return;
UInt32 numBytes;
UInt32 nPackets = myInfo->mNumPacketsToRead;
AudioFileReadPackets (
myInfo->mAudioFile,
false,
&numBytes,
myInfo->mPacketDescs,
myInfo->mCurrentPacket,
&nPackets,
inCompleteAQBuffer->mAudioData
);
if (nPackets > 0) {
inCompleteAQBuffer->mAudioDataByteSize = numBytes;
AudioQueueEnqueueBuffer (
inAQ,
inCompleteAQBuffer,
(myInfo->mPacketDescs ? nPackets : 0),
myInfo->mPacketDescs
);
myInfo->mCurrentPacket += nPackets;
} else {
AudioQueueStop (
myInfo->mQueue,
false
);
myInfo->mDone = true;
}
}
// Instantiate an audio queue object
AudioQueueNewOutput (
&myInfo.mDataFormat,
AQTestBufferCallback,
Using Audio
Playing Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
19&myInfo,
CFRunLoopGetCurrent(),
kCFRunLoopCommonModes,
0,
&myInfo.mQueue
);
Controlling the Playback Level
Audio queue objects give you two ways to control playback level.
To set playback level directly, use the AudioQueueSetParameter function with the
kAudioQueueParam_Volume parameter, as shown in Listing 1-8. Level change takes effect immediately.
Listing 1-8 Setting the playback level directly
Float32 volume = 1; // linear scale, range from 0.0 through 1.0
AudioQueueSetParameter (
myAQstruct.audioQueueObject,
kAudioQueueParam_Volume,
volume
);
You can also set playback level for an audio queue buffer by using the
AudioQueueEnqueueBufferWithParameters function. This lets you assign audio queue settings that are,
in effect, carried by an audio queue buffer as you enqueue it. Such changes take effect when the buffer begins
playing.
In both cases, level changes for an audio queue remain in effect until you change them again.
Indicating Playback Level
You can obtain the current playback level from an audio queue object by:
1. Enabling metering for the audio queue object by setting its
kAudioQueueProperty_EnableLevelMetering property to true
2. Querying the audio queue object’s kAudioQueueProperty_CurrentLevelMeter property
Using Audio
Playing Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
20The value of this property is an array of AudioQueueLevelMeterState structures, one per channel. Listing
1-9 shows this structure:
Listing 1-9 The AudioQueueLevelMeterState structure
typedef struct AudioQueueLevelMeterState {
Float32 mAveragePower;
Float32 mPeakPower;
}; AudioQueueLevelMeterState;
Playing Multiple Sounds Simultaneously
To play multiple sounds simultaneously, create one playback audio queue object for each sound. For each
audio queue, schedule the first buffer of audio to start at the same time using the
AudioQueueEnqueueBufferWithParameters function.
Starting in iOS 3.0, nearly all supported audio formats can be used for simultaneous playback—namely, all
those that can be played using software decoding, as described in Table 1-1 (page 7). For the most
processor-efficient multiple playback, use linear PCM (uncompressed) or IMA4 (compressed) audio.
Playing Sounds with Positioning Using OpenAL
The open-sourced OpenAL audio API, available in iOS in the OpenAL framework, provides an interface optimized
for positioning sounds in a stereo field during playback. Playing, positioning, and moving sounds works just
asit does on other platforms. OpenAL also lets you mix sounds. OpenAL usesthe I/O unit for playback, resulting
in the lowest latency.
For all of these reasons, OpenAL is your best choice for playing sounds in game applications on iOS-based
devices. However, OpenAL is also a good choice for general iOS application audio playback needs.
OpenAL 1.1 support in iOS is built on top of Core Audio. For more information, see OpenAL FAQ for iPhone OS .
For OpenAL documentation, see the OpenAL website at http://openal.org. For sample code, see oalTouch .
Recording Audio
iOS supports audio recording using the AVAudioRecorder class and Audio Queue Services. These interfaces
do the work of connecting to the audio hardware, managing memory, and employing codecs as needed. You
can record audio in any of the formats listed in Table 1-2 (page 8).
Using Audio
Recording Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
21Recording takes place at a system-defined input level in iOS. The system takes input from the audio source
that the user has chosen—the built-in microphone or, if connected, the headset microphone or other input
source.
Recording with the AVAudioRecorder Class
The easiest way to record sound in iOS is with the AVAudioRecorder class, described in AVAudioRecorder
Class Reference . This class provides a highly-streamlined, Objective-C interface that makes it easy to provide
sophisticated features like pausing/resuming recording and handling audio interruptions. At the same time,
you retain complete control over recording format.
To prepare for recording using an audio recorder:
1. Specify a sound file URL.
2. Set up the audio session.
3. Configure the audio recorder’s initial state.
Application launch is a good time to do this part of the setup, as shown in Listing 1-10. Variables such as
soundFileURL and recording in this example are declared in the class interface. (In production code, you
would include appropriate error handling.)
Listing 1-10 Setting up the audio session and the sound file URL
- (void) viewDidLoad {
[super viewDidLoad];
NSString *tempDir = NSTemporaryDirectory ();
NSString *soundFilePath =
[tempDir stringByAppendingString: @"sound.caf"];
NSURL *newURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
self.soundFileURL = newURL;
[newURL release];
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
audioSession.delegate = self;
[audioSession setActive: YES error: nil];
Using Audio
Recording Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
22recording = NO;
playing = NO;
}
To handle interruptions and the completion of recording, add the AVAudioSessionDelegate and
AVAudioRecorderDelegate protocol names to the interface declaration for your implementation. If your
application also does playback, also adopt the AVAudioPlayerDelegate Protocol Reference protocol.
To implement a record method, you can use code such as that shown in Listing 1-11. (In production code, you
would include appropriate error handling.)
Listing 1-11 A record/stop method using the AVAudioRecorder class
- (IBAction) recordOrStop: (id) sender {
if (recording) {
[soundRecorder stop];
recording = NO;
self.soundRecorder = nil;
[recordOrStopButton setTitle: @"Record" forState:
UIControlStateNormal];
[recordOrStopButton setTitle: @"Record" forState:
UIControlStateHighlighted];
[[AVAudioSession sharedInstance] setActive: NO error: nil];
} else {
[[AVAudioSession sharedInstance]
setCategory: AVAudioSessionCategoryRecord
error: nil];
NSDictionary *recordSettings =
[[NSDictionary alloc] initWithObjectsAndKeys:
Using Audio
Recording Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
23[NSNumber numberWithFloat: 44100.0], AVSampleRateKey,
[NSNumber numberWithInt: kAudioFormatAppleLossless], AVFormatIDKey,
[NSNumber numberWithInt: 1], AVNumberOfChannelsKey,
[NSNumber numberWithInt: AVAudioQualityMax],
AVEncoderAudioQualityKey,
nil];
AVAudioRecorder *newRecorder =
[[AVAudioRecorder alloc] initWithURL: soundFileURL
settings: recordSettings
error: nil];
[recordSettings release];
self.soundRecorder = newRecorder;
[newRecorder release];
soundRecorder.delegate = self;
[soundRecorder prepareToRecord];
[soundRecorder record];
[recordOrStopButton setTitle: @"Stop" forState: UIControlStateNormal];
[recordOrStopButton setTitle: @"Stop" forState: UIControlStateHighlighted];
recording = YES;
}
}
For more information on the AVAudioRecorder class, see AVAudioRecorder Class Reference .
Recording with Audio Queue Services
To set up for recording with audio with Audio Queue Services, your application instantiates a recording audio
queue object and provides a callback function. The callback storesincoming audio data in memory for immediate
use or writes it to a file for long-term storage.
Just as with playback, you can obtain the current recording audio level from an audio queue object by querying
its kAudioQueueProperty_CurrentLevelMeter property, as described in “Indicating Playback Level” (page
20).
Using Audio
Recording Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
24For detailed examples of how to use Audio Queue Services to record audio, see “Recording Audio” in Audio
Queue Services Programming Guide . For sample code, see the SpeakHere sample.
Parsing Streamed Audio
To play streamed audio content, such as from a network connection, use Audio File Stream Services in concert
with Audio Queue Services. Audio File Stream Services parses audio packets and metadata from common audio
file container formats in a network bitstream. You can also use it to parse packets and metadata from on-disk
files.
In iOS, you can parse the same audio file and bitstream formats that you can in Mac OS X, as follows:
● MPEG-1 Audio Layer 3, used for .mp3 files
● MPEG-2 ADTS, used for the .aac audio data format
● AIFC
● AIFF
● CAF
● MPEG-4, used for .m4a, .mp4, and .3gp files
● NeXT
● WAVE
Having retrieved audio packets, you can play back the recovered sound in any of the formats supported in
iOS, as listed in Table 1-1 (page 7).
For best performance, network streaming applications should use data from Wi-Fi connections. iOS lets you
determine which networks are reachable and available through its System Configuration framework and its
SCNetworkReachabilityRef opaque type, described in SCNetworkReachability Reference . Forsample code,
see the Reachability sample in the iOS Dev Center.
To connect to a network stream, use interfaces from Core Foundation, such as the one described in
CFHTTPMessage Reference . Parse the network packetsto recover audio packets using Audio File StreamServices.
Then buffer the audio packets and send them to a playback audio queue object.
Audio File Stream Services relies on interfaces from Audio File Services, such as the
AudioFramePacketTranslation structure and the AudioFilePacketTableInfo structure. These are
described in Audio File Services Reference .
For more information on using streams, refer to Audio File Stream Services Reference .
Using Audio
Parsing Streamed Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
25Audio Unit Support in iOS
iOS provides a set of audio processing plug-ins, known as audio units, that you can use in any application. The
interfaces in the Audio Unit framework let you open, connect, and use these audio units.
To use the features of the Audio Unit framework, add the Audio Toolbox framework to your Xcode project and
link against it in any relevant targets. Then add a #import statement
near the top of relevant source files. For detailed information on how to add frameworks to your project, see
“Files in Projects” in Xcode Project Management Guide .
Table 1-5 lists the audio units provided in iOS.
Table 1-5 System-supplied audio units
Audio unit Description
The iPod EQ unit, of type kAudioUnitSubType_AUiPodEQ, provides a
simple, preset-based equalizer you can use in your application. For a
demonstration of how to use this audio unit,see the sample code project
iPhoneMixerEQGraphTest.
iPod Equalizer unit
The 3DMixer unit, oftype kAudioUnitSubType_AU3DMixerEmbedded,
lets you mix multiple audio streams, specify stereo output panning,
manipulate playback rate, and more. OpenAL is built on top of this audio
unit and provides a higher-level API well suited for game apps.
3D Mixer unit
The Multichannel Mixer unit, of type kAudioUnitSubType_-
MultiChannelMixer, lets you mix multiple mono or stereo audio
streams to a single stereo stream. It also supports left/right panning for
each input. For a demonstration of how to use this audio unit, see the
sample code project Audio Mixer (MixerHost).
Multichannel Mixer unit
The Remote I/Ounit, oftype kAudioUnitSubType_RemoteIO, connects
to audio input and output hardware and supports realtime I/O. For
demonstrations of how to use this audio unit,see the sample code project
aurioTouch .
Remote I/O unit
The Voice Processing I/O unit, of type kAudioUnitSubType_-
VoiceProcessingIO, has the characteristics of the I/O unit and adds
echo suppression and other features for two-way communication.
Voice Processing I/O unit
The Generic Output unit, of type kAudioUnitSubType_-
GenericOutput, supports converting to and from linear PCM format;
can be used to start and stop a graph.
Generic Output unit
Using Audio
Audio Unit Support in iOS
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
26Audio unit Description
The Converter unit, of type kAudioUnitSubType_AUConverter, lets
you convert audio data from one format to another. You typically obtain
the features of this audio unit by using the Remote I/O unit, which
incorporates a Converter unit.
Converter unit
For more information on using system audio units, see Audio Unit Hosting Guide for iOS . For reference
documentation, see Audio Unit Framework Reference and Audio Unit Processing Graph Services Reference . The
iOS Dev Center provides two sample-code projects that demonstrate use of system audio units: aurioTouch
and iPhoneMultichannelMixerTest.
Best Practices for iOS Audio
This section lists some important tips for using audio in iOS and describes the best audio data formats for
various uses.
Tips for Using Audio
Table 1-6 lists some important tips to keep in mind when using audio in iOS.
Table 1-6 Audio tips
Tip Action
For AAC, MP3, and ALAC (Apple Lossless) audio, decoding can take place
using hardware-assisted codecs. While efficient, this is limited to one audio
stream at a time. If you need to play multiple sounds simultaneously, store
those sounds using the IMA4 (compressed) or linear PCM (uncompressed)
format.
Use compressed audio
appropriately
The afconvert tool in Mac OS X lets you convert to a wide range of audio
data formats and file types. See “Preferred Audio Formats in iOS” (page 28)
and the afconvert man page.
Convert to the data
format and file format
you need
When playing sound with Audio Queue Services, you write a callback that
sends short segments of audio data to audio queue buffers. In some cases,
loading an entire sound file to memory for playback, which minimizes disk
access, is best. In other cases, loading just enough data at a time to keep
the buffers full is best. Test and evaluate which strategy works best for your
application.
Evaluate audiomemory
issues
Using Audio
Best Practices for iOS Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
27Tip Action
Sample rate and the number of bits per sample have a direct impact on the
size of your audio files. If you need to play many such sounds, or
long-duration sounds, consider reducing these valuesto reduce the memory
footprint of the audio data. For example, rather than use 44.1 kHz sampling
rate for sound effects, you could use a 32 kHz (or possibly lower) sample
rate and still provide reasonable quality.
Using monophonic (single-channel) audio instead of stereo (two channel)
reduces file size. For each sound asset, consider whether mono could suit
your needs.
Reduce audio file sizes
by limiting sample
rates, bit depths, and
channels
Use OpenAL when you want a convenient, high-level interface for positioning
sounds in a stereo field or when you need low latency playback. To parse
audio packetsfrom a file or a network stream, use Audio File Stream Services.
For simple playback of single or multiple sounds, use the AVAudioPlayer
class. For recording to a file, use the AVAudioRecorder class. For audio
chat, use the Voice Processing I/O unit. To play audio resources synced from
a user’s iTunes library, use iPod Library Access. When your sole audio need
is to play alerts and user-interface sound effects, use Core Audio’s System
Sound Services. For other audio applications, including playback ofstreamed
audio, precise synchronization, and access to packets of incoming audio,
use Audio Queue Services.
Pick the appropriate
technology
For the lowest possible playback latency, use OpenAL or use the I/O unit
directly.
Code for low latency
Preferred Audio Formats in iOS
For uncompressed (highest quality) audio, use 16-bit, little endian, linear PCM audio data packaged in a CAF
file. You can convert an audio file to this format in Mac OS X using the afconvert command-line tool, as
shown here:
/usr/bin/afconvert -f caff -d LEI16 {INPUT} {OUTPUT}
The afconvert tool lets you convert to a wide range of audio data formats and file types. See the afconvert
man page, and enter afconvert -h at a shell prompt, for more information.
For compressed audio when playing one sound at a time, and when you don’t need to play audio simultaneously
with the iPod application, use the AAC format packaged in a CAF or m4a file.
Using Audio
Best Practices for iOS Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
28For less memory usage when you need to play multiple sounds simultaneously, use IMA4 (IMA/ADPCM)
compression. This reduces file size but entails minimal CPU impact during decompression. As with linear PCM
data, package IMA4 data in a CAF file.
Using Audio
Best Practices for iOS Audio
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
29Important: This document contains information that used to be in iOS App Programming Guide . The
information in this document has not been updated specifically for iOS 4.0.
Recording and Editing Video
Starting in iOS 3.0, you can record video, with included audio, on supported devices. To display the video
recording interface, create and push a UIImagePickerController object, just asfor displaying the still-camera
interface.
To record video, you must first check that the camera source type
(UIImagePickerControllerSourceTypeCamera) is available and that the movie media type
(kUTTypeMovie) is available for the camera. Depending on the media types you assign to the mediaTypes
property, the picker can directly display the still camera or the video camera, or a selection interface that lets
the user choose.
Using the UIImagePickerControllerDelegate protocol, register as a delegate of the image picker. Your
delegate object receives a completed video recording by way of the
imagePickerController:didFinishPickingMediaWithInfo: method.
On supported devices, you can also pick previously-recorded videos from a user’s photo library.
For more information on using the image picker class, see UIImagePickerController Class Reference . For
information on trimming recorded videos, see UIVideoEditorController Class Reference and
UIVideoEditorControllerDelegate Protocol Reference .
In iOS 4.0 and later, you can record from a device’s camera and display the incoming data live on screen. You
use AVCaptureSession to manage data flow from inputs represented by AVCaptureInput objects (which
mediate input from an AVCaptureDevice) to outputs represented by AVCaptureOutput.
In iOS 4.0 and later, you can edit, assemble, and compose video using existing assets or with new raw materials.
Assets are represented by AVAsset, which you can inspect asynchronously for better performance. You use
AVMutableComposition to compose media from one or more sources, then AVAssetExportSession to
encode output of a composition for delivery.
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
30
Using VideoPlaying Video Files
Important: The information in this section currently reflects the usage of the Media Player framework in
iOS 3.1 and earlier. Please see the headers for information about changes to this framework in iOS 4.0.
iOS supportsthe ability to play back video files directly from your application using the Media Player framework,
described in Media Player Framework Reference . Video playback is supported in full screen mode only and can
be used by game developers who want to play short animations or by any developers who want to play media
files. When you start a video from your application, the media player interface takes over, fading the screen to
black and then fading in the video content. You can play a video with or without user controls for adjusting
playback. Enabling some or all of these controls (shown in Figure 2-1) gives the user the ability to change the
volume, change the playback point, or start and stop the video. If you disable all of these controls, the video
plays until completion.
Figure 2-1 Media player interface with transport controls
To initiate video playback, you must know the URL of the file you want to play. For files your application
provides, this would typically be a pointer to a file in your application’s bundle; however, it can also be a pointer
to a file on a remote server. Use this URL to instantiate a new instance of the MPMoviePlayerController
class. This class presides over the playback of your video file and manages user interactions, such as user taps
in the transport controls (if shown). To start playback, call the play method described in MPMediaPlayback
Protocol Reference .
Listing 2-1 shows a sample method that plays back the video at a specified URL. The play method is an
asynchronous call that returns control to the caller while the movie plays. The movie controller loadsthe movie
in a full-screen view, and animates the movie into place on top of the application’s existing content. When
playback is finished, the movie controller sends a notification received by the application controller object, which
releases the movie controller now that it is no longer needed.
Listing 2-1 Playing full-screen movies
-(void) playMovieAtURL: (NSURL*) theURL {
Using Video
Playing Video Files
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
31MPMoviePlayerController* theMovie =
[[MPMoviePlayerController alloc] initWithContentURL: theURL];
theMovie.scalingMode = MPMovieScalingModeAspectFill;
theMovie.movieControlMode = MPMovieControlModeHidden;
// Register for the playback finished notification
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: @selector(myMovieFinishedCallback:)
name: MPMoviePlayerPlaybackDidFinishNotification
object: theMovie];
// Movie playback is asynchronous, so this method returns immediately.
[theMovie play];
}
// When the movie is done, release the controller.
-(void) myMovieFinishedCallback: (NSNotification*) aNotification
{
MPMoviePlayerController* theMovie = [aNotification object];
[[NSNotificationCenter defaultCenter]
removeObserver: self
name: MPMoviePlayerPlaybackDidFinishNotification
object: theMovie];
// Release the movie instance created in playMovieAtURL:
[theMovie release];
}
For a list of supported video formats, see iOS Technology Overview.
Using Video
Playing Video Files
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
32In iOS 4.0 and later, you can play video using AVPlayer in conjunction with an AVPlayerLayer or an
AVSynchronizedLayer object. You can use AVAudioMix and AVVideoComposition to customize the
audio and video parts of playback respectively. You can also use AVCaptureVideoPreviewLayer to display
video as it is being captured by an input device.
Using Video
Playing Video Files
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
33This table describes the changes to Multimedia Programming Guide .
Date Notes
Clarified usage of AudioServicesPlaySystemSound function in “Playing
UI Sound Effects or Invoking Vibration Using System Sound Services” (page
12).
2010-09-01
Updated Table 1-2 (page 8) for iOS 4.0 by clarifying support for AAC
encoding.
2010-05-27
2010-09-01 | © 2010 Apple Inc. All Rights Reserved.
34
Document Revision HistoryApple Inc.
© 2010 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, iPad, iPhone, iPod, iPod
touch, iTunes, Mac, Mac OS, Objective-C, OS X,
and Xcode are trademarks of Apple Inc.,
registered in the U.S. and other countries.
NeXT is a trademark of NeXT Software, Inc.,
registered in the U.S. and other countries.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
Concepts in Objective-C
ProgrammingContents
About the Basic Programming Concepts for Cocoa and Cocoa Touch 7
At a Glance 7
How to Use This Document 7
Prerequisites 8
See Also 8
Class Clusters 9
Without Class Clusters: Simple Concept but Complex Interface 9
With Class Clusters: Simple Concept and Simple Interface 10
Creating Instances 10
Class Clusters with Multiple Public Superclasses 11
Creating Subclasses Within a Class Cluster 12
A True Subclass 12
True Subclasses: An Example 14
A Composite Object 16
A Composite Object: An Example 16
Class Factory Methods 20
Delegates and Data Sources 22
How Delegation Works 22
The Form of Delegation Messages 24
Delegation and the Application Frameworks 25
Becoming the Delegate of a Framework Class 26
Locating Objects Through the delegate Property 27
Data Sources 27
Implementing a Delegate for a Custom Class 27
Introspection 29
Evaluating Inheritance Relationships 29
Method Implementation and Protocol Conformance 30
Object Comparison 31
Object Allocation 34
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
2Object Initialization 35
The Form of Initializers 35
Issues with Initializers 36
Implementing an Initializer 38
Multiple Initializers and the Designated Initializer 40
Model-View-Controller 43
Roles and Relationships of MVC Objects 43
Model Objects Encapsulate Data and Basic Behaviors 43
View Objects Present Information to the User 44
Controller Objects Tie the Model to the View 44
Combining Roles 45
Types of Cocoa Controller Objects 45
MVC as a Compound Design Pattern 47
Design Guidelines for MVC Applications 50
Model-View-Controller in Cocoa (OS X) 52
Object Modeling 53
Entities 53
Attributes 54
Relationships 55
Relationship Cardinality and Ownership 56
Accessing Properties 57
Keys 57
Values 57
Key Paths 58
Object Mutability 60
Why Mutable and Immutable Object Variants? 60
Programming with Mutable Objects 62
Creating and Converting Mutable Objects 62
Storing and Returning Mutable Instance Variables 63
Receiving Mutable Objects 64
Mutable Objects in Collections 66
Outlets 67
Receptionist Pattern 68
The Receptionist Design Pattern in Practice 68
When to Use the Receptionist Pattern 71
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
3
ContentsTarget-Action 73
The Target 73
The Action 74
Target-Action in the AppKit Framework 75
Controls, Cells, and Menu Items 75
Setting the Target and Action 77
Actions Defined by AppKit 78
Target-Action in UIKit 78
Toll-Free Bridging 80
Document Revision History 83
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
4
ContentsFigures, Tables, and Listings
Class Clusters 9
Figure 1-1 A simple hierarchy for number classes 9
Figure 1-2 A more complete number class hierarchy 10
Figure 1-3 Class cluster architecture applied to number classes 10
Figure 1-4 An object that embeds a cluster object 16
Table 1-1 Class clusters and their public superclasses 11
Table 1-2 Derived methods and their possible implementations 13
Delegates and Data Sources 22
Figure 3-1 The mechanism of delegation 23
Figure 3-2 A more realistic sequence involving a delegate 23
Listing 3-1 Sample delegation methods with return values 24
Listing 3-2 Sample delegation methods returning void 24
Introspection 29
Listing 4-1 Using the class and superclass methods 29
Listing 4-2 Using isKindOfClass: 30
Listing 4-3 Using respondsToSelector: 31
Listing 4-4 Using conformsToProtocol: 31
Listing 4-5 Using isEqual: 32
Listing 4-6 Overriding isEqual: 32
Object Initialization 35
Figure 6-1 Initialization up the inheritance chain 39
Figure 6-2 Interactions of secondary and designated initializers 41
Model-View-Controller 43
Figure 7-1 Traditional version of MVC as a compound pattern 48
Figure 7-2 Cocoa version of MVC as a compound design pattern 48
Figure 7-3 Coordinating controller as the owner of a nib file 50
Object Modeling 53
Figure 8-1 Employee management application object diagram 54
Figure 8-2 Employees table view 55
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
5Figure 8-3 Relationships in the employee management application 56
Figure 8-4 Relationship cardinality 56
Figure 8-5 Object graph for the employee management application 58
Figure 8-6 Employees table view showing department name 59
Object Mutability 60
Listing 9-1 Returning an immutable copy of a mutable instance variable 63
Listing 9-2 Making a snapshot of a potentially mutable object 65
Receptionist Pattern 68
Figure 11-1 Bouncing KVO updates to the main operation queue 69
Listing 11-1 Declaring the receptionist class 69
Listing 11-2 The class factory method for creating a receptionist object 70
Listing 11-3 Handling the KVO notification 71
Listing 11-4 Creating a receptionist object 71
Target-Action 73
Figure 12-1 How the target-action mechanism works in the control-cell architecture 76
Toll-Free Bridging 80
Table 13-1 Data types that can be used interchangeably between Core Foundation and Foundation 81
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
6
Figures, Tables, and ListingsMany of the programmatic interfaces of the Cocoa and Cocoa Touch frameworks only make sense only if you
are aware of the concepts on which they are based. These concepts express the rationale for many of the core
designs of the frameworks. Knowledge of these concepts will illuminate your software-development practices.
Model layer View layer
Controller layer
Application
delegate
System frameworks
At a Glance
This document contains articles that explain central concepts, design patterns, and mechanisms of the Cocoa
and Cocoa Touch frameworks. The articles are arranged in alphabetical order.
How to Use This Document
If you read this document cover-to-cover, you learn important information about Cocoa and Cocoa Touch
application development. However, most readers come to the articles in this document in one of two ways:
● Other documents—especially those that are intended for novice iOS and OS X developers—which link to
these articles.
●
In-line mini-articles (which appear when you click a dash-underlined word or phrase) that have a link to
an article as a “Definitive Discussion.”
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
7
About the Basic Programming Concepts for Cocoa
and Cocoa TouchPrerequisites
Prior programming experience, especially with object-oriented languages, is recommended.
See Also
The Objective-C Programming Language offers further discussion of many of the language-related concepts
covered in this document.
About the Basic Programming Concepts for Cocoa and Cocoa Touch
Prerequisites
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
8Class clusters are a design pattern that the Foundation framework makes extensive use of. Class clusters group
a number of private concrete subclasses under a public abstract superclass. The grouping of classes in this way
simplifies the publicly visible architecture of an object-oriented framework without reducing its functional
richness. Class clusters are based on the Abstract Factory design pattern.
Without Class Clusters: Simple Concept but Complex Interface
To illustrate the class cluster architecture and its benefits, consider the problem of constructing a class hierarchy
that defines objects to store numbers of different types (char, int, float, double). Because numbers of
different types have many features in common (they can be converted from one type to another and can be
represented as strings, for example), they could be represented by a single class. However, their storage
requirements differ,so it’sinefficient to represent them all by the same class. Taking thisfact into consideration,
one could design the class architecture depicted in Figure 1-1 to solve the problem.
Figure 1-1 A simple hierarchy for number classes
Number is the abstract superclass that declares in its methods the operations common to its subclasses.
However, it doesn’t declare an instance variable to store a number. The subclasses declare such instance
variables and share in the programmatic interface declared by Number.
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
9
Class ClustersSo far, this design is relatively simple. However, if the commonly used modifications of these basic C types are
taken into account, the class hierarchy diagram looks more like Figure 1-2.
Figure 1-2 A more complete number class hierarchy
The simple concept—creating a class to hold number values—can easily burgeon to over a dozen classes. The
class cluster architecture presents a design that reflects the simplicity of the concept.
With Class Clusters: Simple Concept and Simple Interface
Applying the class cluster design pattern to this problem yieldsthe class hierarchy in Figure 1-3 (private classes
are in gray).
Figure 1-3 Class cluster architecture applied to number classes
Users of this hierarchy see only one public class, Number, so how is it possible to allocate instances of the
proper subclass? The answer is in the way the abstract superclass handles instantiation.
Creating Instances
The abstract superclass in a class cluster must declare methods for creating instances of its private subclasses.
It’s the superclass’s responsibility to dispense an object of the proper subclass based on the creation method
that you invoke—you don’t, and can’t, choose the class of the instance.
In the Foundation framework, you generally create an object by invoking a +className... method or the
alloc... and init... methods. Taking the Foundation framework’s NSNumber class as an example, you
could send these messages to create number objects:
Class Clusters
With Class Clusters: Simple Concept and Simple Interface
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
10NSNumber *aChar = [NSNumber numberWithChar:’a’];
NSNumber *anInt = [NSNumber numberWithInt:1];
NSNumber *aFloat = [NSNumber numberWithFloat:1.0];
NSNumber *aDouble = [NSNumber numberWithDouble:1.0];
You are not responsible for releasing the objects returned from factory methods. Many classes also provide
the standard alloc... and init... methodsto create objectsthat require you to manage their deallocation.
Each object returned—aChar, anInt, aFloat, and aDouble—may belong to a different private subclass
(and in fact does). Although each object’s class membership is hidden, itsinterface is public, being the interface
declared by the abstract superclass, NSNumber. Although it is not precisely correct, it’s convenient to consider
the aChar, anInt, aFloat, and aDouble objects to be instances of the NSNumber class, because they’re
created by NSNumber class methods and accessed through instance methods declared by NSNumber.
Class Clusters with Multiple Public Superclasses
In the example above, one abstract public class declares the interface for multiple private subclasses. This is a
class cluster in the purest sense. It’s also possible, and often desirable, to have two (or possibly more) abstract
public classes that declare the interface for the cluster. This is evident in the Foundation framework, which
includes the clusters listed in Table 1-1.
Table 1-1 Class clusters and their public superclasses
Class cluster Public superclasses
NSData
NSData
NSMutableData
NSArray
NSArray
NSMutableArray
NSDictionary
NSDictionary
NSMutableDictionary
NSString
NSString
NSMutableString
Class Clusters
Class Clusters with Multiple Public Superclasses
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
11Other clusters of this type also exist, but these clearly illustrate how two abstract nodes cooperate in declaring
the programmatic interface to a class cluster. In each of these clusters, one public node declares methods that
all cluster objects can respond to, and the other node declares methods that are only appropriate for cluster
objects that allow their contents to be modified.
This factoring of the cluster’s interface helps make an object-oriented framework’s programmatic interface
more expressive. For example, imagine an object representing a book that declares this method:
- (NSString *)title;
The book object could return its own instance variable or create a new string object and return that—it doesn’t
matter. It’s clear from this declaration that the returned string can’t be modified. Any attempt to modify the
returned object will elicit a compiler warning.
Creating Subclasses Within a Class Cluster
The class cluster architecture involves a trade-off between simplicity and extensibility: Having a few public
classes stand in for a multitude of private ones makes it easier to learn and use the classes in a framework but
somewhat harder to create subclasses within any of the clusters. However, if it’s rarely necessary to create a
subclass, then the cluster architecture is clearly beneficial. Clusters are used in the Foundation framework in
just these situations.
If you find that a cluster doesn’t provide the functionality your program needs, then a subclass may be in order.
For example, imagine that you want to create an array object whose storage is file-based rather than
memory-based, as in the NSArray class cluster. Because you are changing the underlying storage mechanism
of the class, you’d have to create a subclass.
On the other hand, in some cases it might be sufficient (and easier) to define a class that embeds within it an
object from the cluster. Let’s say that your program needs to be alerted whenever some data is modified. In
this case, creating a simple class that wraps a data object that the Foundation framework defines may be the
best approach. An object of this class could intervene in messages that modify the data, intercepting the
messages, acting on them, and then forwarding them to the embedded data object.
In summary, if you need to manage your object’sstorage, create a true subclass. Otherwise, create a composite
object, one that embeds a standard Foundation framework object in an object of your own design. The following
sections give more detail on these two approaches.
A True Subclass
A new class that you create within a class cluster must:
Class Clusters
Creating Subclasses Within a Class Cluster
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
12● Be a subclass of the cluster’s abstract superclass
● Declare its own storage
● Override all initializer methods of the superclass
● Override the superclass’s primitive methods (described below)
Because the cluster’s abstract superclass is the only publicly visible node in the cluster’s hierarchy, the first
point is obvious. This implies that the new subclass will inherit the cluster’s interface but no instance variables,
because the abstract superclass declares none. Thus the second point: The subclass must declare any instance
variables it needs. Finally, the subclass must override any method it inherits that directly accesses an object’s
instance variables. Such methods are called primitive methods.
A class’s primitive methodsform the basisfor itsinterface. For example, take the NSArray class, which declares
the interface to objects that manage arrays of objects. In concept, an array stores a number of data items, each
of which is accessible by index. NSArray expresses this abstract notion through its two primitive methods,
count and objectAtIndex:. With these methods as a base, other methods—derived methods—can be
implemented; Table 1-2 gives two examples of derived methods.
Table 1-2 Derived methods and their possible implementations
Derived Method Possible Implementation
Find the last object by sending the array object this message: [self
objectAtIndex: ([self count] –1)].
lastObject
Find an object by repeatedly sending the array object an objectAtIndex:
message, each time incrementing the index until all objects in the array have
been tested.
containsObject:
The division of an interface between primitive and derived methods makes creating subclasses easier. Your
subclass must override inherited primitives, but having done so can be sure that all derived methods that it
inherits will operate properly.
The primitive-derived distinction applies to the interface of a fully initialized object. The question of how
init... methods should be handled in a subclass also needs to be addressed.
In general, a cluster’s abstract superclass declares a number of init... and + className methods. As
described in “Creating Instances” (page 10), the abstract class decides which concrete subclass to instantiate
based your choice of init... or + className method. You can consider that the abstract class declares
these methods for the convenience of the subclass. Since the abstract class has no instance variables, it has
no need of initialization methods.
Class Clusters
Creating Subclasses Within a Class Cluster
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
13Your subclass should declare its own init... (if it needs to initialize its instance variables) and possibly +
className methods. It should not rely on any of those that it inherits. To maintain its link in the initialization
chain, it should invoke its superclass’s designated initializer within its own designated initializer method. It
should also override all other inherited initializer methods and implement them to behave in a reasonable
manner. (See ““The Runtime System”“ in The Objective-C Programming Language for a discussion of designated
initializers.) Within a class cluster, the designated initializer of the abstract superclass is always init.
True Subclasses: An Example
Let’s say that you want to create a subclass of NSArray, named MonthArray, that returns the name of a
month given its index position. However, a MonthArray object won’t actually store the array of month names
as an instance variable. Instead, the method that returns a name given an index position (objectAtIndex:)
will return constantstrings. Thus, only twelve string objects will be allocated, no matter how many MonthArray
objects exist in an application.
The MonthArray class is declared as:
#import
@interface MonthArray : NSArray
{
}
+ monthArray;
- (unsigned)count;
- (id)objectAtIndex:(unsigned)index;
@end
Note that the MonthArray class doesn’t declare an init... method because it has no instance variables to
initialize. The count and objectAtIndex: methodssimply cover the inherited primitive methods, as described
above.
The implementation of the MonthArray class looks like this:
#import "MonthArray.h"
@implementation MonthArray
Class Clusters
Creating Subclasses Within a Class Cluster
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
14static MonthArray *sharedMonthArray = nil;
static NSString *months[] = { @"January", @"February", @"March",
@"April", @"May", @"June", @"July", @"August", @"September",
@"October", @"November", @"December" };
+ monthArray
{
if (!sharedMonthArray) {
sharedMonthArray = [[MonthArray alloc] init];
}
return sharedMonthArray;
}
- (unsigned)count
{
return 12;
}
- objectAtIndex:(unsigned)index
{
if (index >= [self count])
[NSException raise:NSRangeException format:@"***%s: index
(%d) beyond bounds (%d)", sel_getName(_cmd), index,
[self count] - 1];
else
return months[index];
}
@end
Because MonthArray overridesthe inherited primitive methods, the derived methodsthat it inherits will work
properly without being overridden. NSArray’s lastObject, containsObject:,
sortedArrayUsingSelector:, objectEnumerator, and other methods work without problems for
MonthArray objects.
Class Clusters
Creating Subclasses Within a Class Cluster
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
15A Composite Object
By embedding a private cluster object in an object of your own design, you create a composite object. This
composite object can rely on the cluster object for its basic functionality, only intercepting messages that the
composite object wants to handle in some particular way. This architecture reduces the amount of code you
must write and lets you take advantage of the tested code provided by the Foundation Framework. Figure 1-4
depicts this architecture.
Figure 1-4 An object that embeds a cluster object
The composite object must declare itself to be a subclass of the cluster’s abstract superclass. As a subclass, it
must override the superclass’s primitive methods. It can also override derived methods, but this isn’t necessary
because the derived methods work through the primitive ones.
The count method of the NSArray class is an example; the intervening object’s implementation of a method
it overrides can be as simple as:
- (unsigned)count {
return [embeddedObject count];
}
However, your object could put code for its own purposes in the implementation of any method it overrides.
A Composite Object: An Example
To illustrate the use of a composite object, imagine you want a mutable array object that tests changes against
some validation criteria before allowing any modification to the array’s contents. The example that follows
describes a class called ValidatingArray, which contains a standardmutable array object. ValidatingArray
overrides all of the primitive methods declared in its superclasses, NSArray and NSMutableArray. It also
declares the array, validatingArray, and init methods, which can be used to create and initialize an
instance:
#import
Class Clusters
Creating Subclasses Within a Class Cluster
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
16@interface ValidatingArray : NSMutableArray
{
NSMutableArray *embeddedArray;
}
+ validatingArray;
- init;
- (unsigned)count;
- objectAtIndex:(unsigned)index;
- (void)addObject:object;
- (void)replaceObjectAtIndex:(unsigned)index withObject:object;
- (void)removeLastObject;
- (void)insertObject:object atIndex:(unsigned)index;
- (void)removeObjectAtIndex:(unsigned)index;
@end
The implementation file shows how, in an init method of the ValidatingArrayclass, the embedded object
is created and assigned to the embeddedArray variable. Messages that simply access the array but don’t
modify its contents are relayed to the embedded object. Messagesthat could change the contents are scrutinized
(here in pseudocode) and relayed only if they pass the hypothetical validation test.
#import "ValidatingArray.h"
@implementation ValidatingArray
- init
{
self = [super init];
if (self) {
embeddedArray = [[NSMutableArray allocWithZone:[self zone]] init];
}
return self;
}
Class Clusters
Creating Subclasses Within a Class Cluster
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
17+ validatingArray
{
return [[[self alloc] init] autorelease];
}
- (unsigned)count
{
return [embeddedArray count];
}
- objectAtIndex:(unsigned)index
{
return [embeddedArray objectAtIndex:index];
}
- (void)addObject:object
{
if (/* modification is valid */) {
[embeddedArray addObject:object];
}
}
- (void)replaceObjectAtIndex:(unsigned)index withObject:object;
{
if (/* modification is valid */) {
[embeddedArray replaceObjectAtIndex:index withObject:object];
}
}
- (void)removeLastObject;
{
if (/* modification is valid */) {
[embeddedArray removeLastObject];
Class Clusters
Creating Subclasses Within a Class Cluster
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
18}
}
- (void)insertObject:object atIndex:(unsigned)index;
{
if (/* modification is valid */) {
[embeddedArray insertObject:object atIndex:index];
}
}
- (void)removeObjectAtIndex:(unsigned)index;
{
if (/* modification is valid */) {
[embeddedArray removeObjectAtIndex:index];
}
}
Class Clusters
Creating Subclasses Within a Class Cluster
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
19Class factory methods are implemented by a class as a convenience for clients. They combine allocation and
initialization in one step and return the created object. However, the client receiving this object does not own
the object and thus (per the object-ownership policy) is not responsible for releasing it. These methods are of
the form + (type)className... (where className excludes any prefix).
Cocoa provides plenty of examples, especially among the “value” classes. NSDate includes the following class
factory methods:
+ (id)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs;
+ (id)dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)secs;
+ (id)dateWithTimeIntervalSince1970:(NSTimeInterval)secs;
And NSData offers the following factory methods:
+ (id)dataWithBytes:(const void *)bytes length:(unsigned)length;
+ (id)dataWithBytesNoCopy:(void *)bytes length:(unsigned)length;
+ (id)dataWithBytesNoCopy:(void *)bytes length:(unsigned)length
freeWhenDone:(BOOL)b;
+ (id)dataWithContentsOfFile:(NSString *)path;
+ (id)dataWithContentsOfURL:(NSURL *)url;
+ (id)dataWithContentsOfMappedFile:(NSString *)path;
Factory methods can be more than a simple convenience. They can not only combine allocation and initialization,
but the allocation can inform the initialization. As an example, let’s say you must initialize a collection object
from a property-list file that encodes any number of elements for the collection (NSString objects, NSData
objects, NSNumber objects, and so on). Before the factory method can know how much memory to allocate
for the collection, it must read the file and parse the property list to determine how many elements there are
and what object type these elements are.
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
20
Class Factory MethodsAnother purpose for a classfactory method isto ensure that a certain class(NSWorkspace, for example) vends
a singleton instance. Although an init... method could verify that only one instance exists at any one time
in a program, it would require the prior allocation of a “raw” instance and then, in memory-managed code,
would have to release that instance. A factory method, on the other hand, gives you a way to avoid blindly
allocating memory for an object that you might not use, as in the following example:
static AccountManager *DefaultManager = nil;
+ (AccountManager *)defaultManager {
if (!DefaultManager) DefaultManager = [[self allocWithZone:NULL] init];
return DefaultManager;
}
Class Factory Methods
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
21A delegate is an object that acts on behalf of, or in coordination with, another object when that object encounters
an event in a program. The delegating object is often a responder object—that is, an object inheriting from
NSResponder in AppKit or UIResponder in UIKit—that is responding to a user event. The delegate is an
object that is delegated control of the user interface for that event, or is at least asked to interpret the event
in an application-specific manner.
To better appreciate the value of delegation, it helps to consider an off-the-shelf Cocoa object such as a text
field (an instance of NSTextField or UITextField) or a table view (an instance of NSTableView or
UITableView ). These objects are designed to fulfill a specific role in a generic fashion; a window object in
the AppKit framework, for example, responds to mouse manipulations of its controls and handles such things
as closing, resizing, and moving the physical window. This restricted and generic behavior necessarily limits
what the object can know about how an event affects (or will affect) something elsewhere in the application,
especially when the affected behavior isspecific to your application. Delegation provides a way for your custom
object to communicate application-specific behavior to the off-the-shelf object.
The programming mechanism of delegation gives objects a chance to coordinate their appearance and state
with changes occurring elsewhere in a program, changes usually brought about by user actions. More
importantly, delegation makes it possible for one object to alter the behavior of another object without the
need to inherit from it. The delegate is almost always one of your custom objects, and by definition it
incorporates application-specific logic that the generic and delegating object cannot possibly know itself.
How Delegation Works
The design of the delegation mechanism is simple—see Figure 3-1 (page 23). The delegating class has an
outlet or property, usually one that is named delegate; if it is an outlet, it includes methods for setting and
accessing the value of the outlet. It also declares, without implementing, one or more methods that constitute
a formal protocol or an informal protocol. A formal protocol that uses optional methods—a feature ofObjective-C
2.0—is the preferred approach, but both kinds of protocols are used by the Cocoa frameworks for delegation.
In the informal protocol approach, the delegating class declares methods on a category of NSObject, and the
delegate implements only those methods in which it has an interest in coordinating itself with the delegating
object or affecting that object’s default behavior. If the delegating class declares a formal protocol, the delegate
may choose to implement those methods marked optional, but it must implement the required ones.
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
22
Delegates and Data SourcesDelegation follows a common design, illustrated by Figure 3-1.
Figure 3-1 The mechanism of delegation
User just clicked close button;
should window close?
No
windowShouldClose:
Don't close. The window
has unsaved data.
windowDelegate
The methods of the protocol mark significant events handled or anticipated by the delegating object. This
object wants either to communicate these events to the delegate or, for impending events, to request input
or approval from the delegate. For example, when a user clicks the close button of a window in OS X, the
window object sends the windowShouldClose: message to its delegate; this gives the delegate the
opportunity to veto or defer the closing of the window if, for example, the window has associated data that
must be saved (see Figure 3-2).
Figure 3-2 A more realistic sequence involving a delegate
Yes
windowShouldClose:
➌
➊
➍ ➋
aWindow aDelegate
The delegating object sends a message only if the delegate implements the method. It makes this discovery
by invoking the NSObjectmethod respondsToSelector: in the delegate first.
Delegates and Data Sources
How Delegation Works
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
23The Form of Delegation Messages
Delegation methods have a conventional form. They begin with the name of the AppKit or UIKit object doing
the delegating—application, window, control, and so on; this name is in lower-case and without the “NS” or
“UI” prefix. Usually (but not always) this object name is followed by an auxiliary verb indicative of the temporal
status of the reported event. This verb, in other words, indicates whether the event is about to occur (“Should”
or “Will”) or whether it has just occurred (“Did” or “Has”). This temporal distinction helps to categorize those
messagesthat expect a return value and those that don’t. Listing 3-1 includes a few AppKit delegation methods
that expect a return value.
Listing 3-1 Sample delegation methods with return values
- (BOOL)application:(NSApplication *)sender
openFile:(NSString *)filename; // NSApplication
- (BOOL)application:(UIApplication *)application
handleOpenURL:(NSURL *)url; // UIApplicationDelegate
- (UITableRowIndexSet *)tableView:(NSTableView *)tableView
willSelectRows:(UITableRowIndexSet *)selection; // UITableViewDelegate
- (NSRect)windowWillUseStandardFrame:(NSWindow *)window
defaultFrame:(NSRect)newFrame; // NSWindow
The delegate that implements these methods can block the impending event (by returning NO in the first two
methods) or alter a suggested value (the index set and the frame rectangle in the last two methods). It can
even defer an impending event; for example, the delegate implementing the
applicationShouldTerminate:method can delay application termination by returning NSTerminateLater.
Other delegation methods are invoked by messagesthat don’t expect a return value and so are typed to return
void. These messages are purely informational, and the method names often contain “Did”, “Will”, or some
other indication of a transpired or impending event. Listing 3-2 shows a few examples of these kinds of
delegation method.
Listing 3-2 Sample delegation methods returning void
- (void) tableView:(NSTableView*)tableView
mouseDownInHeaderOfTableColumn:(NSTableColumn *)tableColumn; // NSTableView
- (void)windowDidMove:(NSNotification *)notification; // NSWindow
- (void)application:(UIApplication *)application
willChangeStatusBarFrame:(CGRect)newStatusBarFrame; //
UIApplication
Delegates and Data Sources
The Form of Delegation Messages
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
24- (void)applicationWillBecomeActive:(NSNotification *)notification; //
NSApplication
There are a couple of things to note about this last group of methods. The first is that an auxiliary verb of “Will”
(as in the third method) does not necessarily mean that a return value is expected. In this case, the event is
imminent and cannot be blocked, but the message gives the delegate an opportunity to prepare the program
for the event.
The other point of interest concernsthe second and last method declarationsin Listing 3-2 . The sole parameter
of each of these methods is an NSNotification object, which means that these methods are invoked as the
result of the posting of a particular notification. For example, the windowDidMove: method is associated with
the NSWindow notification NSWindowDidMoveNotification. It’s important to understand the relationship
of notifications to delegation messages in AppKit. The delegating object automatically makes its delegate an
observer of all notifications it posts. All the delegate needs to do is implement the associated method to get
the notification.
To make an instance of your custom class the delegate of an AppKit object, simply connect the instance to the
delegate outlet or property in Interface Builder. Or you can set it programmatically through the delegating
object’s setDelegate: method or delegate property, preferably early on, such as in the awakeFromNib
or applicationDidFinishLaunching: method.
Delegation and the Application Frameworks
The delegating object in a Cocoa or Cocoa Touch application is often a responder object such as a
UIApplication, NSWindow, or NSTableView object. The delegate object itself istypically, but not necessarily,
an object, often a custom object, that controls some part of the application (that is, a coordinating controller
object). The following AppKit classes define a delegate:
● NSApplication
● NSBrowser
● NSControl
● NSDrawer
● NSFontManager
● NSFontPanel
● NSMatrix
● NSOutlineView
● NSSplitView
Delegates and Data Sources
Delegation and the Application Frameworks
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
25● NSTableView
● NSTabView
● NSText
● NSTextField
● NSTextView
● NSWindow
The UIKit framework also uses delegation extensively and always implements it using formal protocols. The
application delegate is extremely important in an application running in iOS because it must respond to
application-launch, application-quit, low-memory, and other messages from the application object. The
application delegate must adopt the UIApplicationDelegate protocol.
Delegating objects do not (and should not) retain their delegates. However, clients of delegating objects
(applications, usually) are responsible for ensuring that their delegates are around to receive delegation
messages. To do this, they may have to retain the delegate in memory-managed code. This precaution applies
equally to data sources, notification observers, and targets of action messages. Note that in a garbage-collection
environment, the reference to the delegate is strong because the retain-cycle problem does not apply.
Some AppKit classes have a more restricted type of delegate called a modal delegate . Objects of these classes
(NSOpenPanel, for example) run modal dialogs that invoke a handler method in the designated delegate
when the user clicksthe dialog’s OK button. Modal delegates are limited in scope to the operation of the modal
dialog.
Becoming the Delegate of a Framework Class
A framework class or any other classthat implements delegation declares a delegate property and a protocol
(usually a formal protocol). The protocol liststhe required and optional methodsthat the delegate implements.
For an instance of your class to function as the delegate of a framework object, it must do the following:
● Set your object asthe delegate (by assigning it to the delegate property). You can do this programmatically
or through Interface Builder.
●
If the protocol is formal, declare that your class adopts the protocol in the class definition. For example:
@interface MyControllerClass : UIViewController {
●
Implement all required methods of the protocol and any optional methods that you want to participate
in.
Delegates and Data Sources
Delegation and the Application Frameworks
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
26Locating Objects Through the delegate Property
The existence of delegates has other programmatic uses. For example, with delegates it is easy for two
coordinating controllers in the same program to find and communicate with each other. For example, the
object controlling the application overall can find the controller of the application’sinspector window (assuming
it’s the current key window) using code similar to the following:
id winController = [[NSApp keyWindow] delegate];
And your code can find the application-controller object—by definition, the delegate of the global application
instance—by doing something similar to the following:
id appController = [NSApp delegate];
Data Sources
A data source is like a delegate except that, instead of being delegated control of the user interface, it is
delegated control of data. A data source is an outlet held by NSView and UIView objects such as table views
and outline views that require a source from which to populate their rows of visible data. The data source for
a view is usually the same object that acts as its delegate, but it can be any object. As with the delegate, the
data source must implement one or more methods of an informal protocol to supply the view with the data
it needs and, in more advanced implementations, to handle data that users directly edit in such views.
As with delegates, data sources are objectsthat must be present to receive messagesfrom the objectsrequesting
data. The application that uses them must ensure their persistence, retaining them if necessary in
memory-managed code.
Data sources are responsible for the persistence of the objectsthey hand out to user-interface objects. In other
words, they are responsible for the memory management of those objects. However, whenever a view object
such as an outline view or table view accesses the data from a data source, it retains the objects as long as it
uses the data. But it does not use the data for very long. Typically it holds on to the data only long enough to
display it.
Implementing a Delegate for a Custom Class
To implement a delegate for your custom class, complete the following steps:
● Declare the delegate accessor methods in your class header file.
Delegates and Data Sources
Data Sources
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
27- (id)delegate;
- (void)setDelegate:(id)newDelegate;
●
Implement the accessor methods. In a memory-managed program, to avoid retain cycles, the setter method
should not retain or copy your delegate.
- (id)delegate {
return delegate;
}
- (void)setDelegate:(id)newDelegate {
delegate = newDelegate;
}
In a garbage-collected environment, where retain cycles are not a problem, you should not make the
delegate a weak reference (by using the __weak type modifier). For more on retain cycles, see Advanced
MemoryManagement ProgrammingGuide . Formore on weak referencesin garbage collection,see “Garbage
Collection for Cocoa Essentials” in Garbage Collection Programming Guide .
● Declare a formal or informal protocol containing the programmatic interface for the delegate. Informal
protocols are categories on the NSObject class. If you declare a formal protocol for your delegate, make
sure you mark groups of optional methods with the @optional directive.
“The Form of Delegation Messages” (page 24) gives advice for naming your own delegation methods.
● Before invoking a delegation method, make sure the delegate implements it by sending it a
respondsToSelector: message.
- (void)someMethod {
if ( [delegate respondsToSelector:@selector(operationShouldProceed)]
) {
if ( [delegate operationShouldProceed] ) {
// do something appropriate
}
}
}
The precaution is necessary only for optional methods in a formal protocol or methods of an informal
protocol.
Delegates and Data Sources
Implementing a Delegate for a Custom Class
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
28Introspection is a powerful feature of object-oriented languages and environments, and introspection in
Objective-C and Cocoa is no exception. Introspection refersto the capability of objectsto divulge details about
themselves as objects at runtime. Such details include an object’s place in the inheritance tree, whether it
conforms to a specific protocol, and whether it responds to a certain message. The NSObject protocol and
class define many introspection methodsthat you can use to query the runtime in order to characterize objects.
Used judiciously, introspection makes an object-oriented program more efficient and robust. It can help you
avoid message-dispatch errors, erroneous assumptions of object equality, and similar problems. The following
sections show how you might effectively use the NSObject introspection methods in your code.
Evaluating Inheritance Relationships
Once you know the class an object belongs to, you probably know quite a bit about the object. You might
know what its capabilities are, what attributes it represents, and what kinds of messages it can respond to.
Even if after introspection you are unfamiliar with the classto which an object belongs, you now know enough
to not send it certain messages.
The NSObject protocol declares several methods for determining an object’s position in the class hierarchy.
These methods operate at different granularities. The class and superclass instance methods, for example,
return the Class objects representing the class and superclass, respectively, of the receiver. These methods
require you to compare one Class object with another. Listing 4-1 gives a simple (one might say trivial)
example of their use.
Listing 4-1 Using the class and superclass methods
// ...
while ( id anObject = [objectEnumerator nextObject] ) {
if ( [self class] == [anObject superclass] ) {
// do something appropriate...
}
}
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
29
IntrospectionNote: Sometimes you use the class or superclass methods to obtain an appropriate receiver
for a class message.
More commonly, to check an object’s class affiliation, you would send it a isKindOfClass: or
isMemberOfClass: message. The former method returns whether the receiver is an instance of a given class
or an instance of any class that inherits from that class. A isMemberOfClass: message, on the other hand,
tells you if the receiver is an instance of the specified class. The isKindOfClass: method is generally more
useful because from it you can know at once the complete range of messages you can send to an object.
Consider the code snippet in Listing 4-2.
Listing 4-2 Using isKindOfClass:
if ([item isKindOfClass:[NSData class]]) {
const unsigned char *bytes = [item bytes];
unsigned int length = [item length];
// ...
}
By learning that the object item inheritsfrom the NSData class, this code knowsit can send it the NSDatabytes
and lengthmessages. The difference between isKindOfClass: and isMemberOfClass: becomes apparent
if you assume that item is an instance of NSMutableData. If you use isMemberOfClass: instead of
isKindOfClass:, the code in the conditionalized block is never executed because item is not an instance
of NSData but rather of NSMutableData, a subclass of NSData.
Method Implementation and Protocol Conformance
Two of the more powerful introspection methods of NSObject are respondsToSelector: and
conformsToProtocol:. These methodstell you, respectively, whether an object implements a certain method
and whether an object conforms to a specified formal protocol (that is, adopts the protocol, if necessary, and
implements all the methods of the protocol).
You use these methodsin a similarsituation in your code. They enable you to discover whethersome potentially
anonymous object can respond appropriately to a particular message or set of messages before you send it
any of those messages. By making this check before sending a message, you can avoid the risk of runtime
exceptionsresulting from unrecognized selectors. The AppKit framework implementsinformal protocols—the
basis of delegation—by checking whether delegates implement a delegation method (using
respondsToSelector:) prior to invoking that method.
Introspection
Method Implementation and Protocol Conformance
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
30Listing 4-3 illustrates how you might use the respondsToSelector: method in your code.
Listing 4-3 Using respondsToSelector:
- (void)doCommandBySelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {
[self performSelector:aSelector withObject:nil];
} else {
[_client doCommandBySelector:aSelector];
}
}
Listing 4-4 illustrates how you might use the conformsToProtocol: method in your code.
Listing 4-4 Using conformsToProtocol:
// ...
if (!([((id)testObject) conformsToProtocol:@protocol(NSMenuItem)])) {
NSLog(@"Custom MenuItem, '%@', not loaded; it must conform to the
'NSMenuItem' protocol.\n", [testObject class]);
[testObject release];
testObject = nil;
}
Object Comparison
Although they are not strictly introspection methods, the hash and isEqual: methods fulfill a similar role.
They are indispensable runtime toolsfor identifying and comparing objects. But instead of querying the runtime
for information about an object, they rely on class-specific comparison logic.
The hash and isEqual: methods, both declared by the NSObject protocol, are closely related. The hash
method must be implemented to return an integer that can be used as a table addressin a hash table structure.
If two objects are equal (as determined by the isEqual: method), they must have the same hash value. If
your object could be included in collections such as NSSet objects, you need to define hash and verify the
invariant that if two objects are equal, they return the same hash value. The default NSObject implementation
of isEqual: simply checks for pointer equality.
Introspection
Object Comparison
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
31Using the isEqual: method is straightforward; it compares the receiver against the object supplied as a
parameter. Object comparison frequently informsruntime decisions about whatshould be done with an object.
As Listing 4-5 illustrates, you can use isEqual: to decide whether to perform an action, in this case to save
user preferences that have been modified.
Listing 4-5 Using isEqual:
- (void)saveDefaults {
NSDictionary *prefs = [self preferences];
if (![origValues isEqual:prefs])
[Preferences savePreferencesToDefaults:prefs];
}
If you are creating a subclass, you might need to override isEqual: to add further checksfor points of equality.
The subclass might define an extra attribute that has to be the same value in two instances for them to be
considered equal. For example, say you create a subclass of NSObject called MyWidget that contains two
instance variables, name and data. Both of these must be the same value for two instances of MyWidget to
be considered equal. Listing 4-6 illustrates how you might implement isEqual: for the MyWidget class.
Listing 4-6 Overriding isEqual:
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return [self isEqualToWidget:other];
}
- (BOOL)isEqualToWidget:(MyWidget *)aWidget {
if (self == aWidget)
return YES;
if (![(id)[self name] isEqual:[aWidget name]])
return NO;
if (![[self data] isEqualToData:[aWidget data]])
return NO;
return YES;
Introspection
Object Comparison
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
32}
This isEqual: method first checks for pointer equality, then class equality, and finally invokes an object
comparator whose name indicates the class of object involved in the comparison. This type of comparator,
which forcestype checking of the object passed in, is a common convention in Cocoa; the isEqualToString:
method of the NSString class and the isEqualToTimeZone: method of the NSTimeZone class are but two
examples. The class-specific comparator—isEqualToWidget: in this case—performs the checks for name
and data equality.
In all isEqualToType: methods of the Cocoa frameworks, nil is not a valid parameter and implementations
of these methods may raise an exception upon receiving a nil. However, for backward compatibility, isEqual:
methods of the Cocoa frameworks do accept nil, returning NO.
Introspection
Object Comparison
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
33When you allocate an object, part of what happens is what you might expect, given the term. Cocoa allocates
enough memory for the object from a region of application virtual memory. To calculate how much memory
to allocate, it takes the object’s instance variables into account—including their types and order—as specified
by the object’s class.
To allocate an object, you send the message alloc or allocWithZone: to the object’s class. In return, you
get a “raw” (uninitialized) instance of the class. The alloc variant of the method uses the application’s default
zone. A zone is a page-aligned area of memory for holding related objects and data allocated by an application.
See Advanced Memory Management Programming Guide for more information on zones.
An allocation message does other important things besides allocating memory:
●
It sets the object’s retain count to one.
●
It initializes the object’s isainstance variable to point to the object’s class, a runtime object in its own
right that is compiled from the class definition.
●
It initializes all other instance variables to zero (or to the equivalent type for zero, such as nil, NULL, and
0.0).
An object’s isa instance variable is inherited from NSObject, so it is common to all Cocoa objects. After
allocation sets isa to the object’s class, the object is integrated into the runtime’s view of the inheritance
hierarchy and the current network of objects (class and instance) that constitute a program. Consequently an
object can find whatever information it needs at runtime, such as another object’s place in the inheritance
hierarchy, the protocols that other objects conform to, and the location of the method implementations it can
perform in response to messages.
In summary, allocation not only allocates memory for an object but initializes two small but very important
attributes of any object: its isa instance variable and itsretain count. It also sets all remaining instance variables
to zero. But the resulting object is not yet usable. Initializing methods such as init must yet initialize objects
with their particular characteristics and return a functional object.
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
34
Object AllocationInitialization sets the instance variables of an object to reasonable and useful initial values. It can also allocate
and prepare other global resources needed by the object, loading them if necessary from an external source
such as a file. Every object that declares instance variables should implement an initializing method—unless
the defaultset-everything-to-zero initialization issufficient. If an object does not implement an initializer, Cocoa
invokes the initializer of the nearest ancestor instead.
The Form of Initializers
NSObject declares the init prototype for initializers; it is an instance method typed to return an object of
type id. Overriding init is fine for subclasses that require no additional data to initialize their objects. But
often initialization depends on external data to set an object to a reasonable initial state. For example, say you
have an Account class; to initialize an Account object appropriately requires a unique account number, and
this must be supplied to the initializer. Thus initializers can take one or more parameters; the only requirement
is that the initializing method begins with the letters “init”. (The stylistic convention init... is sometimes
used to refer to initializers.)
Note: Instead of implementing an initializer with parameters, a subclass can implement only a
simple init method and then use “set” accessor methods immediately after initialization to set the
object to a useful initial state. (Accessor methods enforce encapsulation of object data by setting
and getting the values of instance variables.) Or, if the subclass uses properties and the related access
syntax, it may assign values to the properties immediately after initialization.
Cocoa has plenty of examples of initializers with parameters. Here are a few (with the defining class in
parentheses):
- (id)initWithArray:(NSArray *)array; (from NSSet)
- (id)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate
*)anotherDate; (from NSDate)
- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)aStyle
backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag; (from NSWindow)
- (id)initWithFrame:(NSRect)frameRect; (from NSControl and NSView)
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
35
Object InitializationThese initializers are instance methods that begin with “init” and return an object of the dynamic type id.
Other than that, they follow the Cocoa conventions for multiparameter methods, often using WithType: or
FromSource: before the first and most important parameter.
Issues with Initializers
Although init... methods are required by their method signature to return an object, that object is not
necessarily the one that was most recently allocated—the receiver of the init... message. In other words,
the object you get back from an initializer might not be the one you thought was being initialized.
Two conditions prompt the return of something other than the just-allocated object. The first involves two
related situations: when there must be a singleton instance or when the defining attribute of an object must
be unique. Some Cocoa classes—NSWorkspace, for instance—allow only one instance in a program; a class
in such a case must ensure (in an initializer or, more likely, in a class factory method) that only one instance is
created, returning this instance if there is any further request for a new one.
A similar situation arises when an object is required to have an attribute that makes it unique. Recall the
hypothetical Account class mentioned earlier. An account of any sort must have a unique identifier. If the
initializer for this class—say, initWithAccountID:—is passed an identifier that has already been associated
with an object, it must do two things:
● Release the newly allocated object (in memory-managed code)
● Return the Account object previously initialized with this unique identifier
By doing this, the initializer ensures the uniqueness of the identifier while providing what was asked for: an
Account instance with the requested identifier.
Sometimes an init... method cannot perform the initialization requested. For example, an initFromFile:
method expects to initialize an object from the contents of a file, the path to which is passed as a parameter.
But if no file exists at that location, the object cannot be initialized. A similar problem happens if an
initWithArray: initializer is passed an NSDictionary object instead of an NSArray object. When an
init... method cannot initialize an object, it should:
● Release the newly allocated object (in memory-managed code)
● Return nil
Returning nil from an initializer indicates that the requested object cannot be created. When you create an
object, you should generally check whether the returned value is nil before proceeding:
Object Initialization
Issues with Initializers
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
36id anObject = [[MyClass alloc] init];
if (anObject) {
[anObject doSomething];
// more messages...
} else {
// handle error
}
Because an init... method might return nil or an object other than the one explicitly allocated, it is
dangerous to use the instance returned by alloc or allocWithZone: instead of the one returned by the
initializer. Consider the following code:
id myObject = [MyClass alloc];
[myObject init];
[myObject doSomething];
The init method in this example could have returned nil or could have substituted a different object. Because
you can send a message to nil without raising an exception, nothing would happen in the former case except
(perhaps) a debugging headache. But you should always rely on the initialized instance instead of the “raw”
just-allocated one. Therefore, you should nest the allocation message inside the initialization message and
test the object returned from the initializer before proceeding.
id myObject = [[MyClass alloc] init];
if ( myObject ) {
[myObject doSomething];
} else {
// error recovery...
}
Once an object is initialized, you should not initialize it again. If you attempt a reinitialization, the framework
class of the instantiated object often raises an exception. For example, the second initialization in this example
would result in NSInvalidArgumentException being raised.
NSString *aStr = [[NSString alloc] initWithString:@"Foo"];
aStr = [aStr initWithString:@"Bar"];
Object Initialization
Issues with Initializers
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
37Implementing an Initializer
There are several critical rules to follow when implementing an init... method that serves as a class’s sole
initializer or, if there are multiple initializers, its designated initializer (described in “Multiple Initializers and the
Designated Initializer” (page 40)):
● Always invoke the superclass (super) initializer first.
● Check the object returned by the superclass. If it is nil, then initialization cannot proceed; return nil to
the receiver.
● When initializing instance variables that are references to objects, retain or copy the object as necessary
(in memory-managed code).
● After setting instance variables to valid initial values, return self unless:
●
It was necessary to return a substituted object, in which case release the freshly allocated object first
(in memory-managed code).
● A problem prevented initialization from succeeding, in which case return nil.
- (id)initWithAccountID:(NSString *)identifier {
if ( self = [super init] ) {
Account *ac = [accountDictionary objectForKey:identifier];
if (ac) { // object with that ID already exists
[self release];
return [ac retain];
}
if (identifier) {
accountID = [identifier copy]; // accountID is instance variable
[accountDictionary setObject:self forKey:identifier];
return self;
} else {
[self release];
return nil;
}
} else
return nil;
}
Object Initialization
Implementing an Initializer
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
38Note: Although, for the sake of simplicity, this example returns nil if the parameter is nil, the
better Cocoa practice is to raise an exception.
It isn’t necessary to initialize all instance variables of an object explicitly, just those that are necessary to make
the object functional. The default set-to-zero initialization performed on an instance variable during allocation
is often sufficient. Make sure that you retain or copy instance variables, as required for memory management.
The requirement to invoke the superclass’s initializer as the first action is important. Recall that an object
encapsulates not only the instance variables defined by its class but the instance variables defined by all of its
ancestor classes. By invoking the initializer of super first, you help to ensure that the instance variables defined
by classes up the inheritance chain are initialized first. The immediate superclass, in its initializer, invokes the
initializer of its superclass, which invokes the main init... method of its superclass, and so on (see Figure
6-1). The proper order of initialization is critical because the later initializations of subclasses may depend on
superclass-defined instance variables being initialized to reasonable values.
Figure 6-1 Initialization up the inheritance chain
super
super
Class A
Class B
Class C
inherits from
inherits from
self
- (id)initWithName:birthday:
- (id)initWithName:
Instance variables:
NSString *name:
Instance variables:
NSString *name:
NSDate *dob:
- (id)initWithName:birthday:ssn:
Instance variables:
NSString *name:
NSDate *dob:
NSNumber *ssn:
sets
sets
sets
Object Initialization
Implementing an Initializer
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
39Inherited initializers are a concern when you create a subclass. Sometimes a superclass init... method
sufficiently initializes instances of your class. But because it is more likely it won’t, you should override the
superclass’s initializer. If you don’t, the superclass’s implementation is invoked, and because the superclass
knows nothing about your class, your instances may not be correctly initialized.
Multiple Initializers and the Designated Initializer
A class can define more than one initializer. Sometimes multiple initializers let clients of the class provide the
input for the same initialization in different forms. The NSSet class, for example, offers clientsseveral initializers
that accept the same data in different forms; one takes an NSArray object, another a counted list of elements,
and another a nil-terminated list of elements:
- (id)initWithArray:(NSArray *)array;
- (id)initWithObjects:(id *)objects count:(unsigned)count;
- (id)initWithObjects:(id)firstObj, ...;
Some subclasses provide convenience initializers that supply default values to an initializer that takes the full
complement of initialization parameters. Thisinitializer is usually the designated initializer, the most important
initializer of a class. For example, assume there is a Task class and it declares a designated initializer with this
signature:
- (id)initWithTitle:(NSString *)aTitle date:(NSDate *)aDate;
The Task class might include secondary, or convenience, initializersthatsimply invoke the designated initializer,
passing it default values for those parameters the secondary initializer doesn’t explicitly request. This example
shows a designated initializer and a secondary initializer.
- (id)initWithTitle:(NSString *)aTitle {
return [self initWithTitle:aTitle date:[NSDate date]];
}
- (id)init {
return [self initWithTitle:@"Task"];
}
Object Initialization
Multiple Initializers and the Designated Initializer
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
40The designated initializer plays an important role for a class. It ensures that inherited instance variables are
initialized by invoking the designated initializer of the superclass. It is typically the init... method that has
the most parameters and that does most of the initialization work, and it is the initializer that secondary
initializers of the class invoke with messages to self.
When you define a subclass, you must be able to identify the designated initializer of the superclass and invoke
it in your subclass’s designated initializer through a message to super. You must also make sure that inherited
initializers are covered in some way. And you may provide as many convenience initializers as you deem
necessary. When designing the initializers of your class, keep in mind that designated initializers are chained
to each other through messages to super; whereas other initializers are chained to the designated initializer
of their class through messages to self.
An example will make this clearer. Let’s say there are three classes, A, B, and C; class B inherits from class A,
and class C inherits from class B. Each subclass adds an attribute as an instance variable and implements an
init... method—the designated initializer—to initialize this instance variable. They also define secondary
initializers and ensure that inherited initializers are overridden, if necessary. Figure 6-2 illustrates the initializers
of all three classes and their relationships.
Figure 6-2 Interactions of secondary and designated initializers
- (id)init
super
super
Class A
Class B
Class C
inherits from
inherits from
self
- (id)init
- (id)initWithTitle:
self
- (id)initWithTitle:
- (id)initWithTitle:date:
Object Initialization
Multiple Initializers and the Designated Initializer
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
41The designated initializer for each class is the initializer with the most coverage; it is the method that initializes
the attribute added by the subclass. The designated initializer is also the init... method that invokes the
designated initializer of the superclass in a message to super. In this example, the designated initializer of
class C, initWithTitle:date:, invokesthe designated initializer of itssuperclass, initWithTitle:, which
in turn invokes the init method of class A. When creating a subclass, it’s always important to know the
designated initializer of the superclass.
Although designated initializers are thus connected up the inheritance chain through messages to super,
secondary initializers are connected to their class’s designated initializer through messagesto self. Secondary
initializers (as in this example) are frequently overridden versions of inherited initializers. Class C overrides
initWithTitle: to invoke its designated initializer, passing it a default date. This designated initializer, in
turn, invokes the designated initializer of class B, which is the overridden method, initWithTitle:. If you
sent an initWithTitle: message to objects of class B and class C, you’d be invoking different method
implementations. On the other hand, if class C did not override initWithTitle: and you sent the message
to an instance of class C, the class B implementation would be invoked. Consequently, the C instance would
be incompletely initialized (since it would lack a date). When creating a subclass, it’s important to make sure
that all inherited initializers are adequately covered.
Sometimes the designated initializer of a superclass may be sufficient for the subclass, and so there is no need
for the subclass to implement its own designated initializer. Other times, a class’s designated initializer may
be an overridden version of its superclass's designated initializer. This is frequently the case when the subclass
needs to supplement the work performed by the superclass’s designated initializer, even though the subclass
does not add any instance variables of its own (or the instance variables it does add don’t require explicit
initialization).
Object Initialization
Multiple Initializers and the Designated Initializer
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
42The Model-View-Controller design pattern (MVC) is quite old. Variations of it have been around at least since
the early days of Smalltalk. It is a high-level pattern in that it concerns itself with the global architecture of an
application and classifies objects according to the general rolesthey play in an application. It is also a compound
pattern in that it comprises several, more elemental patterns.
Object-oriented programs benefit in several ways by adapting the MVC design pattern for their designs. Many
objectsin these programstend to be more reusable and their interfacestend to be better defined. The programs
overall are more adaptable to changing requirements—in other words, they are more easily extensible than
programs that are not based on MVC. Moreover, many technologies and architectures in Cocoa—such as
bindings, the document architecture, and scriptability—are based on MVC and require that your custom objects
play one of the roles defined by MVC.
Roles and Relationships of MVC Objects
The MVC design pattern considersthere to be three types of objects: model objects, view objects, and controller
objects. The MVC pattern defines the roles that these types of objects play in the application and their lines
of communication. When designing an application, a major step is choosing—or creating custom classes
for—objects that fall into one of these three groups. Each of the three types of objects is separated from the
others by abstract boundaries and communicates with objects of the other types across those boundaries.
Model Objects Encapsulate Data and Basic Behaviors
Model objects represent special knowledge and expertise. They hold an application’s data and define the logic
that manipulates that data. A well-designed MVC application has all its important data encapsulated in model
objects. Any data that is part of the persistent state of the application (whether that persistent state is stored
in files or databases) should reside in the model objects once the data is loaded into the application. Because
they represent knowledge and expertise related to a specific problem domain, they tend to be reusable.
Ideally, a model object has no explicit connection to the user interface used to present and edit it. For example,
if you have a model object that represents a person (say you are writing an address book), you might want to
store a birthdate. That’s a good thing to store in your Person model object. However, storing a date format
string or other information on how that date is to be presented is probably better off somewhere else.
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
43
Model-View-ControllerIn practice, thisseparation is not alwaysthe best thing, and there issome room for flexibility here, but in general
a model object should not be concerned with interface and presentation issues. One example where a bit of
an exception isreasonable is a drawing application that has model objectsthat represent the graphics displayed.
It makes sense for the graphic objects to know how to draw themselves because the main reason for their
existence is to define a visual thing. But even in this case, the graphic objects should not rely on living in a
particular view or any view at all, and they should not be in charge of knowing when to draw themselves. They
should be asked to draw themselves by the view object that wants to present them.
View Objects Present Information to the User
A view object knows how to display, and might allow users to edit, the data from the application’s model. The
view should not be responsible forstoring the data it is displaying. (This does not mean the view never actually
stores data it’s displaying, of course. A view can cache data or do similar tricks for performance reasons). A
view object can be in charge of displaying just one part of a model object, or a whole model object, or even
many different model objects. Views come in many different varieties.
View objects tend to be reusable and configurable, and they provide consistency between applications. In
Cocoa, the AppKit framework defines a large number of view objects and provides many of them in the Interface
Builder library. By reusing the AppKit’s view objects, such as NSButton objects, you guarantee that buttons
in your application behave just like buttonsin any other Cocoa application, assuring a high level of consistency
in appearance and behavior across applications.
A view should ensure it is displaying the model correctly. Consequently, it usually needsto know about changes
to the model. Because model objects should not be tied to specific view objects, they need a generic way of
indicating that they have changed.
Controller Objects Tie the Model to the View
A controller object acts as the intermediary between the application's view objects and its model objects.
Controllers are often in charge of making sure the views have access to the model objects they need to display
and act as the conduit through which views learn about changes to the model. Controller objects can also
perform set-up and coordinating tasks for an application and manage the life cycles of other objects.
In a typical Cocoa MVC design, when users enter a value or indicate a choice through a view object, that value
or choice is communicated to a controller object. The controller object might interpret the user input in some
application-specific way and then either may tell a model object what to do with thisinput—for example, "add
a new value" or "delete the current record"—or it may have the model object reflect a changed value in one
of its properties. Based on this same user input, some controller objects might also tell a view object to change
an aspect of its appearance or behavior, such as telling a button to disable itself. Conversely, when a model
object changes—say, a new data source is accessed—the model object usually communicates that change to
a controller object, which then requests one or more view objects to update themselves accordingly.
Model-View-Controller
Roles and Relationships of MVC Objects
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
44Controller objects can be either reusable or nonreusable, depending on their general type. “Types of Cocoa
Controller Objects” (page 45) describes the different types of controller objects in Cocoa.
Combining Roles
One can merge the MVC roles played by an object, making an object, for example, fulfill both the controller
and view roles—in which case, it would be called a view controller. In the same way, you can also have
model-controller objects. For some applications, combining roles like this is an acceptable design.
A model controller is a controller that concerns itself mostly with the model layer. It “owns” the model; its
primary responsibilities are to manage the model and communicate with view objects. Action methods that
apply to the model as a whole are typically implemented in a model controller. The document architecture
provides a number of these methods for you; for example, an NSDocument object (which is a central part of
the document architecture) automatically handles action methods related to saving files.
A view controller is a controller that concerns itself mostly with the view layer. It “owns” the interface (the
views); its primary responsibilities are to manage the interface and communicate with the model. Action
methods concerned with data displayed in a view are typically implemented in a view controller. An
NSWindowController object (also part of the document architecture) is an example of a view controller.
“Design Guidelinesfor MVC Applications” (page 50) offerssome design advice concerning objects with merged
MVC roles.
FurtherReading: Document-BasedApplicationsOverview discussesthedistinctionbetweenamodel
controller and a view controller from another perspective.
Types of Cocoa Controller Objects
“Controller Objects Tie the Model to the View” (page 44) sketches the abstract outline of a controller object,
but in practice the picture is far more complex. In Cocoa there are two general kinds of controller objects:
mediating controllers and coordinating controllers. Each kind of controller object is associated with a different
set of classes and each provides a different range of behaviors.
A mediating controller is typically an object that inherits from the NSControllerclass. Mediating controller
objects are used in the Cocoa bindings technology. They facilitate—or mediate—the flow of data between
view objects and model objects.
Model-View-Controller
Types of Cocoa Controller Objects
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
45iOS Note: AppKit implements the NSController class and its subclasses. These classes and the
bindings technology are not available in iOS.
Mediating controllers are typically ready-made objects that you drag from the Interface Builder library. You
can configure these objects to establish the bindings between properties of view objects and properties of
the controller object, and then between those controller properties and specific properties of a model object.
As a result, when users change a value displayed in a view object, the new value is automatically communicated
to a model object forstorage—via the mediating controller; and when a property of a model changesits value,
that change is communicated to a view for display. The abstract NSController class and its concrete
subclasses—NSObjectController, NSArrayController, NSUserDefaultsController, and
NSTreeController—provide supporting features such as the ability to commit and discard changes and
the management of selections and placeholder values.
A coordinating controller istypically an NSWindowController or NSDocumentControllerobject (available
only in AppKit), or an instance of a custom subclass of NSObject. Its role in an application is to oversee—or
coordinate—the functioning of the entire application or of part of the application,such asthe objects unarchived
from a nib file. A coordinating controller provides services such as:
● Responding to delegation messages and observing notifications
● Responding to action messages
● Managing the life cycle of owned objects (for example, releasing them at the proper time)
● Establishing connections between objects and performing other set-up tasks
NSWindowController and NSDocumentController are classes that are part of the Cocoa architecture for
document-based applications. Instances of these classes provide default implementations for several of the
services listed above, and you can create subclasses of them to implement more application-specific behavior.
You can even use NSWindowController objects to manage windows in an application that is not based on
the document architecture.
A coordinating controller frequently owns the objects archived in a nib file. As File’s Owner, the coordinating
controller is external to the objects in the nib file and manages those objects. These owned objects include
mediating controllers as well as window objects and view objects. See “MVC as a Compound Design
Pattern” (page 47) for more on coordinating controllers as File's Owner.
Instances of custom NSObject subclasses can be entirely suitable as coordinating controllers. These kinds of
controller objects combine both mediating and coordinating functions. For their mediating behavior, they
make use of mechanismssuch astarget-action, outlets, delegation, and notificationsto facilitate the movement
of data between view objects and model objects. They tend to contain a lot of glue code and, because that
code is exclusively application-specific, they are the least reusable kind of object in an application.
Model-View-Controller
Types of Cocoa Controller Objects
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
46Further Reading: For more on the Cocoa bindings technology, see Cocoa Bindings Programming
Topics.
MVC as a Compound Design Pattern
Model-View-Controller is a design pattern that is composed of several more basic design patterns. These basic
patterns work together to define the functional separation and paths of communication that are characteristic
of an MVC application. However, the traditional notion of MVC assigns a set of basic patterns different from
those that Cocoa assigns. The difference primarily lies in the roles given to the controller and view objects of
an application.
In the original (Smalltalk) conception, MVC is made up of the Composite, Strategy, and Observer patterns.
● Composite—The view objectsin an application are actually a composite of nested viewsthat work together
in a coordinated fashion (that is, the view hierarchy). These display components range from a window to
compound views, such as a table view, to individual views, such as buttons. User input and display can
take place at any level of the composite structure.
● Strategy—A controller object implements the strategy for one or more view objects. The view object
confines itself to maintaining its visual aspects, and it delegates to the controller all decisions about the
application-specific meaning of the interface behavior.
● Observer—A model object keeps interested objects in an application—usually view objects—advised of
changes in its state.
The traditional way the Composite, Strategy, and Observer patterns work together is depicted by Figure 7-1:
The user manipulates a view at some level of the composite structure and, as a result, an event is generated.
A controller object receives the event and interprets it in an application-specific way—that is, it applies a
strategy. This strategy can be to request (via message) a model object to change its state or to request a view
Model-View-Controller
MVC as a Compound Design Pattern
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
47object (at some level of the composite structure) to change its behavior or appearance. The model object, in
turn, notifies all objects who have registered as observers when its state changes; if the observer is a view
object, it may update its appearance accordingly.
Figure 7-1 Traditional version of MVC as a compound pattern
User action
Update
Get changed state
Update
Strategy
Controller
Composite
View
Notify
Observer
Model
The Cocoa version of MVC as a compound pattern has some similarities to the traditional version, and in fact
it is quite possible to construct a working application based on the diagram in Figure 7-1. By using the bindings
technology, you can easily create a Cocoa MVC application whose views directly observe model objects to
receive notifications of state changes. However, there is a theoretical problem with this design. View objects
and model objects should be the most reusable objects in an application. View objects represent the "look
and feel" of an operating system and the applications that system supports; consistency in appearance and
behavior is essential, and that requires highly reusable objects. Model objects by definition encapsulate the
data associated with a problem domain and perform operations on that data. Design-wise, it's best to keep
model and view objects separate from each other, because that enhances their reusability.
In most Cocoa applications, notifications of state changes in model objects are communicated to view objects
through controller objects. Figure 7-2 shows this different configuration, which appears much cleaner despite
the involvement of two more basic design patterns.
Figure 7-2 Cocoa version of MVC as a compound design pattern
User action
Update
Update
Notify
Mediator
Strategy
Controller
View Model
Command
Composite
Observer
Model-View-Controller
MVC as a Compound Design Pattern
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
48The controller object in this compound design pattern incorporatesthe Mediator pattern as well asthe Strategy
pattern; it mediates the flow of data between model and view objects in both directions. Changes in model
state are communicated to view objects through the controller objects of an application. In addition, view
objects incorporate the Command pattern through their implementation of the target-action mechanism.
Note: The target-action mechanism, which enables view objects to communicate user input and
choices, can be implemented in both coordinating and mediating controller objects. However, the
design of the mechanism differs in each controller type. For coordinating controllers, you connect
the view object to its target (the controller object) in Interface Builder and specify an action selector
that must conform to a certain signature. Coordinating controllers, by virtue of being delegates of
windows and the global application object, can also be in the responder chain. The bindings
mechanism used by mediating controllers also connects view objects to targets and allows action
signatures with a variable number of parameters of arbitrary types. Mediating controllers, however,
aren’t in the responder chain.
There are practical reasons as well as theoretical ones for the revised compound design pattern depicted in
Figure 7-2, especially when it comesto the Mediator design pattern. Mediating controllers derive from concrete
subclasses of NSController, and these classes, besides implementing the Mediator pattern, offer many
features that applications should take advantage of, such as the management of selections and placeholder
values. And if you opt not to use the bindings technology, your view object could use a mechanism such as
the Cocoa notification center to receive notifications from a model object. But this would require you to create
a custom view subclass to add the knowledge of the notifications posted by the model object.
Model-View-Controller
MVC as a Compound Design Pattern
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
49In a well-designed Cocoa MVC application, coordinating controller objects often own mediating controllers,
which are archived in nib files. Figure 7-3 shows the relationships between the two types of controller objects.
Figure 7-3 Coordinating controller as the owner of a nib file
Owns
Coordinating
Controller
Nib file
Data flow
Data flow
View
Mediating
Controller
Model
Design Guidelines for MVC Applications
The following guidelines apply to Model-View-Controller considerations in the design of applications:
● Although you can use an instance of a custom subclass of NSObject as a mediating controller, there's no
reason to go through all the work required to make it one. Use instead one of the ready-made
NSController objects designed for the Cocoa bindings technology; that is, use an instance of
NSObjectController, NSArrayController, NSUserDefaultsController, or
NSTreeController—or a custom subclass of one of these concrete NSController subclasses.
However, if the application is very simple and you feel more comfortable writing the glue code needed
to implement mediating behavior using outlets and target-action, feel free to use an instance of a custom
NSObject subclass as a mediating controller. In a custom NSObject subclass, you can also implement a
mediating controller in the NSController sense, using key-value coding, key-value observing, and the
editor protocols.
Model-View-Controller
Design Guidelines for MVC Applications
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
50● Although you can combine MVC roles in an object, the best overall strategy is to keep the separation
between roles. This separation enhances the reusability of objects and the extensibility of the program
they're used in. If you are going to merge MVC roles in a class, pick a predominant role for that class and
then (for maintenance purposes) use categories in the same implementation file to extend the class to
play other roles.
● A goal of a well-designed MVC application should be to use as many objects as possible that are
(theoretically, at least) reusable. In particular, view objects and model objects should be highly reusable.
(The ready-made mediating controller objects, of course, are reusable.) Application-specific behavior is
frequently concentrated as much as possible in controller objects.
● Although it is possible to have views directly observe models to detect changes in state, it is best not to
do so. A view object should always go through a mediating controller object to learn about changes in
an model object. The reason is two-fold:
●
If you use the bindings mechanism to have view objects directly observe the properties of model
objects, you bypass all the advantages that NSController and its subclasses give your application:
selection and placeholder management as well as the ability to commit and discard changes.
●
If you don't use the bindings mechanism, you have to subclass an existing view classto add the ability
to observe change notifications posted by a model object.
● Strive to limit code dependency in the classes of your application. The greater the dependency a class has
on another class, the less reusable it is. Specific recommendations vary by the MVC roles of the two classes
involved:
● A view classshouldn't depend on a model class(although this may be unavoidable with some custom
views).
● A view class shouldn't have to depend on a mediating controller class.
● A model class shouldn't depend on anything other than other model classes.
● A mediating controller class shouldn’t depend on a model class (although, like views, this may be
necessary if it's a custom controller class).
● A mediating controller class shouldn't depend on view classes or on coordinating controller classes.
● A coordinating controller class depends on classes of all MVC role types.
●
If Cocoa offers an architecture that solves a programming problem, and this architecture assigns MVC
roles to objects of specific types, use that architecture. It will be much easier to put your project together
if you do. The document architecture, for example, includes an Xcode project template that configures
an NSDocument object (per-nib model controller) as File's Owner.
Model-View-Controller
Design Guidelines for MVC Applications
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
51Model-View-Controller in Cocoa (OS X)
The Model-View-Controller design pattern is fundamental to many Cocoa mechanisms and technologies. As
a consequence, the importance of using MVC in object-oriented design goes beyond attaining greater reusability
and extensibility for your own applications. If your application is to incorporate a Cocoa technology that is
MVC-based, your application will work best if its design also follows the MVC pattern. It should be relatively
painless to use these technologies if your application has a good MVC separation, but it will take more effort
to use such a technology if you don’t have a good separation.
Cocoa in OS X includes the following architectures, mechanisms, and technologies that are based on
Model-View-Controller:
● Document architecture. In this architecture, a document-based application consists of a controller object
for the entire application (NSDocumentController), a controller object for each document window
(NSWindowController), and an object that combines controller and model roles for each document
(NSDocument).
● Bindings. MVC is central to the bindings technology of Cocoa. The concrete subclasses of the abstract
NSController provide ready-made controller objects that you can configure to establish bindings
between view objects and properly designed model objects.
● Application scriptability. When designing an application to make it scriptable, it is essential not only that
it follow the MVC design pattern but that your application’s model objects are properly designed. Scripting
commandsthat access application state and request application behaviorshould usually be sent to model
objects or controller objects.
● Core Data. The Core Data framework manages graphs of model objects and ensures the persistence of
those objects by saving them to (and retrieving them from) a persistentstore. Core Data istightly integrated
with the Cocoa bindings technology. The MVC and object modeling design patterns are essential
determinants of the Core Data architecture.
● Undo. In the undo architecture, model objects once again play a central role. The primitive methods of
model objects (which are usually its accessor methods) are often where you implement undo and redo
operations. The view and controller objects of an action may also be involved in these operations; for
example, you might have such objects give specific titles to the undo and redo menu items, or you might
have them undo selections in a text view.
Model-View-Controller
Model-View-Controller in Cocoa (OS X)
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
52This section defines terms and presents examples of object modeling and key-value coding that are specific
to Cocoa bindings and the Core Data framework. Understanding terms such as key paths is fundamental to
using these technologies effectively. This section is recommended reading if you are new to object-oriented
design or key-value coding.
When using the Core Data framework, you need a way to describe your model objects that does not depend
on views and controllers. In a good reusable design, views and controllers need a way to access model properties
without imposing dependencies between them. The Core Data framework solves this problem by borrowing
concepts and terms from database technology—specifically, the entity-relationship model.
Entity-relationship modeling is a way of representing objects typically used to describe a data source’s data
structures in a way that allows those data structures to be mapped to objects in an object-oriented system.
Note that entity-relationship modeling isn’t unique to Cocoa; it’s a popular discipline with a set of rules and
terms that are documented in database literature. It is a representation that facilitates storage and retrieval of
objects in a data source. A data source can be a database, a file, a web service, or any other persistent store.
Because it is not dependent on any type of data source it can also be used to represent any kind of object and
its relationship to other objects.
In the entity-relationship model, the objects that hold data are called entities, the components of an entity are
called attributes, and the referencesto other data-bearing objects are called relationships. Together, attributes
and relationships are known as properties. With these three simple components (entities, attributes, and
relationships), you can model systems of any complexity.
Cocoa uses a modified version of the traditional rules of entity-relationship modeling referred to in this document
as object modeling . Object modeling is particularly useful in representing model objects in the
Model-View-Controller (MVC) design pattern. Thisis notsurprising because even in a simple Cocoa application,
models are typically persistent—that is, they are stored in a data container such as a file.
Entities
Entities are model objects. In the MVC design pattern, model objects are the objects in your application that
encapsulate specified data and provide methods that operate on that data. They are usually persistent but
more importantly, model objects are not dependent on how the data is displayed to the user.
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
53
Object ModelingFor example, a structured collection of model objects (an object model) can be used to represent a company’s
customer base, a library of books, or a network of computers. A library book has attributes—such as the book
title, ISBN number, and copyright date—and relationships to other objects—such as the author and library
member. In theory, if the parts of a system can be identified, the system can be expressed as an object model.
Figure 8-1 shows an example object model used in an employee management application. In this model,
Department models a department and Employee models an employee.
Figure 8-1 Employee management application object diagram
Department
name
budget
Employee
firstName
lastName
salary
Attributes
Attributes represent structures that contain data. An attribute of an object may be a simple value, such as a
scalar (for example, an integer, float, or double value), but can also be a C structure (for example an array
of char values or an NSPoint structure) or an instance of a primitive class (such as, NSNumber, NSData, or
NSColor in Cocoa). Immutable objects such as NSColor are usually considered attributes too. (Note that Core
Data natively supports only a specific set of attribute types, as described in NSAttributeDescription Class
Reference . You can, however, use additional attribute types, as described in “Non-Standard Persistent Attributes”
in Core Data Programming Guide .)
Object Modeling
Attributes
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
54In Cocoa, an attribute typically corresponds to a model’s instance variable or accessor method. For example,
Employee has firstName, lastName, and salary instance variables. In an employee management application,
you might implement a table view to display a collection of Employee objects and some of their attributes, as
shown in Figure 8-2. Each row in the table correspondsto an instance of Employee, and each column corresponds
to an attribute of Employee.
Figure 8-2 Employees table view
Relationships
Not all properties of a model are attributes—some properties are relationshipsto other objects. Your application
is typically modeled by multiple classes. At runtime, your object model is a collection of related objects that
make up an object graph. These are typically the persistent objects that your users create and save to some
data container or file before terminating the application (asin a document-based application). The relationships
between these model objects can be traversed at runtime to access the properties of the related objects.
For example, in the employee management application, there are relationships between an employee and the
department in which the employee works, and between an employee and the employee’s manager. Because
a manager is also an employee, the employee–manager relationship is an example of a reflexive relationship—a
relationship from an entity to itself.
Relationships are inherently bidirectional, so conceptually at least there are also relationships between a
department and the employees that work in the department, and an employee and the employee’s direct
reports. Figure 8-3 (page 56) illustrates the relationships between a Department and an Employee entity, and
the Employee reflexive relationship. In this example, the Department entity’s “employees” relationship is the
inverse of the Employee entity’s “department” relationship. It is possible, however, for relationships to be
navigable in only one direction—for there to be no inverse relationship. If, for example, you are never interested
in finding out from a department object what employees are associated with it, then you do not have to model
Object Modeling
Relationships
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
55that relationship. (Note that although thisistrue in the general case, Core Data may impose additional constraints
over general Cocoa object modeling—not modeling the inverse should be considered an extremely advanced
option.)
Figure 8-3 Relationships in the employee management application
Department
name
budget
Employee
firstName
lastName
salary
department employees manager
directReports
Relationship Cardinality and Ownership
Every relationship has a cardinality ; the cardinality tells you how many destination objects can (potentially)
resolve the relationship. If the destination object is a single object, then the relationship is called a to-one
relationship . If there may be more than one object in the destination, then the relationship is called a to-many
relationship .
Relationships can be mandatory or optional. A mandatory relationship is one where the destination is
required—for example, every employee must be associated with a department. An optional relationship is, as
the name suggests, optional—for example, not every employee has direct reports. So the directReports
relationship depicted in Figure 8-4 (page 56) is optional.
It is also possible to specify a range for the cardinality. An optional to-one relationship has a range 0-1. An
employee may have any number of direct reports, or a range that specifies a minimum and a maximum, for
example, 0-15, which also illustrates an optional to-many relationship.
Figure 8-4 illustrates the cardinalities in the employee management application. The relationship between an
Employee object and a Department object is a mandatory to-one relationship—an employee must belong to
one, and only one, department. The relationship between a Department and its Employee objectsis an optional
to-many relationship (represented by a “*”). The relationship between an employee and a manager is an
optional to-one relationship (denoted by the range 0-1)—top-ranking employees do not have managers.
Figure 8-4 Relationship cardinality
1 department employees * 0..1 manager
* directReports
Department
name
budget
Employee
firstName
lastName
salary
Note also that destination objects of relationships are sometimes owned and sometimes shared.
Object Modeling
Relationships
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
56Accessing Properties
In order for models, views, and controllers to be independent of each other, you need to be able to access
properties in a way that is independent of a model’s implementation. This is accomplished by using key-value
pairs.
Keys
You specify properties of a model using a simple key, often a string. The corresponding view or controller uses
the key to look up the corresponding attribute value. This design enforces the notion that the attribute itself
doesn’t necessarily contain the data—the value can be indirectly obtained or derived.
Key-value coding is used to perform thislookup; it is a mechanism for accessing an object’s propertiesindirectly
and, in certain contexts, automatically. Key-value coding works by using the names of the object’s
properties—typically itsinstance variables or accessor methods—as keysto accessthe values of those properties.
For example, you might obtain the name of a Department object using a name key. If the Department object
either has an instance variable or a method called name then a value for the key can be returned (if it doesn’t
have either, an error is returned). Similarly, you might obtain Employee attributes using the firstName,
lastName, and salary keys.
Values
All values for a particular attribute of a given entity are of the same data type. The data type of an attribute is
specified in the declaration of its corresponding instance variable or in the return value of its accessor method.
For example, the data type of the Department object name attribute may be an NSString object in Objective-C.
Note that key-value coding returns only object values. If the return type or the data type for the specific accessor
method or instance variable used to supply the value for a specified key is not an object, then an NSNumber
or NSValue object is created for that value and returned in its place. If the name attribute of Department is of
type NSString, then, using key-value coding, the value returned for the name key of a Department object is
an NSString object. If the budget attribute of Department is of type float, then, using key-value coding,
the value returned for the budget key of a Department object is an NSNumber object.
Similarly, when you set a value using key-value coding, if the data type required by the appropriate accessor
or instance variable for the specified key is not an object, then the value is extracted from the passed object
using the appropriate -typeValue method.
The value of a to-one relationship is simply the destination object of that relationship. For example, the value
of the department property of an Employee object is a Department object. The value of a to-many relationship
is the collection object. The collection can be a set or an array. If you use Core Data it is a set; otherwise, it is
Object Modeling
Accessing Properties
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
57typically an array) that contains the destination objects of that relationship. For example, the value of the
employees property of an Department object is a collection containing Employee objects. Figure 8-5 shows
an example object graph for the employee management application.
Figure 8-5 Object graph for the employee management application
Department
name: "Marketing"
budget: 2000000
employees
Collection
Collection
Employee
firstName: "Toni"
lastName: "Lau"
salary: 7000
manager
department
directReports
Employee
firstName: "Joe"
lastName: "Jackson"
salary: 5000
manager
department
directReports
Key Paths
A key path is a string of dot-separated keysthatspecify a sequence of object propertiesto traverse. The property
of the first key is determined by, and each subsequent key is evaluated relative to, the previous property. Key
paths allow you to specify the properties of related objects in a way that is independent of the model
implementation. Using key paths you can specify the path through an object graph, of whatever depth, to a
specific attribute of a related object.
The key-value coding mechanism implements the lookup of a value given a key path similar to key-value pairs.
For example, in the employee-management application you might access the name of a Department via an
Employee object using the department.name key path where department is a relationship of Employee
and name is an attribute of Department. Key paths are useful if you want to display an attribute of a destination
Object Modeling
Accessing Properties
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
58entity. For example, the employee table view in Figure 8-6 is configured to display the name of the employee’s
department object, not the department object itself. Using Cocoa bindings, the value of the Department
column is bound to department.name of the Employee objects in the displayed array.
Figure 8-6 Employees table view showing department name
Not every relationship in a key path necessarily has a value. For example, the manager relationship can be
nil if the employee is the CEO. In this case, the key-value coding mechanism does not break—it simply stops
traversing the path and returns an appropriate value, such as nil.
Object Modeling
Accessing Properties
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
59Cocoa objects are either mutable or immutable. You cannot change the encapsulated values of immutable
objects; once such an object is created, the value it represents remains the same throughout the object’s life.
But you can change the encapsulated value of a mutable object at any time. The following sections explain
the reasons for having mutable and immutable variants of an object type, describe the characteristics and
side-effects of object mutability, and recommend how best to handle objects when their mutability is an issue.
Why Mutable and Immutable Object Variants?
Objects by default are mutable. Most objects allow you to change their encapsulated data through setter
accessor methods. For example, you can change the size, positioning, title, buffering behavior, and other
characteristics of an NSWindow object. A well-designed model object—say, an object representing a customer
record—requires setter methods to change its instance data.
The Foundation framework adds some nuance to this picture by introducing classes that have mutable and
immutable variants. The mutable subclasses are typically subclasses of their immutable superclass and have
“Mutable” embedded in the class name. These classes include the following:
NSMutableArray
NSMutableDictionary
NSMutableSet
NSMutableIndexSet
NSMutableCharacterSet
NSMutableData
NSMutableString
NSMutableAttributedString
NSMutableURLRequest
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
60
Object MutabilityNote: Except for NSMutableParagraphStyle in the AppKit framework, the Foundation framework
currently defines all explicitly named mutable classes. However, any Cocoa framework can potentially
have its own mutable and immutable class variants.
Although these classes have atypical names, they are closer to the mutable norm than their immutable
counterparts. Why this complexity? What purpose does having an immutable variant of a mutable objectserve?
Consider a scenario where all objects are capable of being mutated. In your application you invoke a method
and are handed back a reference to an object representing a string. You use this string in your user interface
to identify a particular piece of data. Now another subsystem in your application gets its own reference to that
same string and decidesto mutate it. Suddenly your label has changed out from under you. Things can become
even more dire if, for instance, you get a reference to an array that you use to populate a table view. The user
selects a row corresponding to an object in the array that has been removed by some code elsewhere in the
program, and problems ensue. Immutability is a guarantee that an object won’t unexpectedly change in value
while you’re using it.
Objects that are good candidates for immutability are ones that encapsulate collections of discrete values or
contain values that are stored in buffers (which are themselves kinds of collections, either of characters or
bytes). But not all such value objects necessarily benefit from having mutable versions. Objects that contain a
single simple value, such as instances of NSNumber or NSDate, are not good candidates for mutability. When
the represented value changes in these cases, it makes more sense to replace the old instance with a new
instance.
Performance is also a reason for immutable versions of objects representing things such as strings and
dictionaries. Mutable objectsfor basic entitiessuch asstrings and dictionaries bring some overhead with them.
Because they must dynamically manage a changeable backing store—allocating and deallocating chunks of
memory as needed—mutable objects can be less efficient than their immutable counterparts.
Although in theory immutability guarantees that an object’s value is stable, in practice this guarantee isn’t
always assured. A method may choose to hand out a mutable object under the return type of its immutable
variant; later, it may decide to mutate the object, possibly violating assumptions and choices the recipient has
made based on the earlier value. The mutability of an object itself may change as it undergoes various
transformations. For example, serializing a property list (using the NSPropertyListSerialization class)
does not preserve the mutability aspect of objects, only their general kind—a dictionary, an array, and so on.
Thus, when you deserialize this property list, the resulting objects might not be of the same class asthe original
objects. For instance, what was once an NSMutableDictionary object might now be a NSDictionary
object.
Object Mutability
Why Mutable and Immutable Object Variants?
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
61Programming with Mutable Objects
When the mutability of objects is an issue, it’s best to adopt some defensive programming practices. Here are
a few general rules or guidelines:
● Use a mutable variant of an object when you need to modify its contents frequently and incrementally
after it has been created.
● Sometimes it’s preferable to replace one immutable object with another; for example, most instance
variables that hold string values should be assigned immutable NSString objects that are replaced with
setter methods.
● Rely on the return type for indications of mutability.
●
If you have any doubts about whether an object is, or should be, mutable, go with immutable.
This section explores the gray areas in these guidelines, discussing typical choices you have to make when
programming with mutable objects. It also gives an overview of methods in the Foundation framework for
creating mutable objects and for converting between mutable and immutable object variants.
Creating and Converting Mutable Objects
You can create a mutable object through the standard nested alloc-init message—for example:
NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] init];
However, many mutable classes offer initializers and factory methodsthat let you specify the initial or probable
capacity of the object, such as the arrayWithCapacity: class method of NSMutableArray:
NSMutableArray *mutArray = [NSMutableArray arrayWithCapacity:[timeZones count]];
The capacity hint enables more efficient storage of the mutable object’s data. (Because the convention for
class factory methods is to return autoreleased instances, be sure to retain the object if you wish to keep it
viable in your code.)
You can also create a mutable object by making a mutable copy of an existing object of that general type. To
do so, invoke the mutableCopy method that each immutable super class of a Foundation mutable class
implements:
NSMutableSet *mutSet = [aSet mutableCopy];
Object Mutability
Programming with Mutable Objects
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
62In the other direction, you can send copy to a mutable object to make an immutable copy of the object.
Many Foundation classes with immutable and mutable variants include methods for converting between the
variants, including:
●
typeWithType:—for example, arrayWithArray:
● setType:—for example, setString: (mutable classes only)
● initWithType:copyItems:—for example, initWithDictionary:copyItems:
Storing and Returning Mutable Instance Variables
In Cocoa development you often have to decide whether to make an instance variable mutable or immutable.
For an instance variable whose value can change, such as a dictionary or string, when is it appropriate to make
the object mutable? And when is it better to make the object immutable and replace it with another object
when its represented value changes?
Generally, when you have an object whose contents change wholesale, it’s better to use an immutable object.
Strings (NSString) and data objects (NSData) usually fall into this category. If an object is likely to change
incrementally, it is a reasonable approach to make it mutable. Collections such as arrays and dictionaries fall
into this category. However, the frequency of changes and the size of the collection should be factors in this
decision. For example, if you have a small array that seldom changes, it’s better to make it immutable.
There are a couple of other considerations when deciding on the mutability of a collection held as an instance
variable:
●
If you have a mutable collection that is frequently changed and that you frequently hand out to clients
(that is, you return it directly in a getter accessor method), you run the risk of mutating something that
your clients might have a reference to. If this risk is probable, the instance variable should be immutable.
●
If the value of the instance variable frequently changes but you rarely return it to clientsin getter methods,
you can make the instance variable mutable but return an immutable copy of it in your accessor method;
in memory-managed programs, this object would be autoreleased (Listing 9-1).
Listing 9-1 Returning an immutable copy of a mutable instance variable
@interface MyClass : NSObject {
// ...
NSMutableSet *widgets;
}
// ...
@end
Object Mutability
Programming with Mutable Objects
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
63@implementation MyClass
- (NSSet *)widgets {
return (NSSet *)[[widgets copy] autorelease];
}
One sophisticated approach for handling mutable collections that are returned to clients is to maintain a flag
that records whether the object is currently mutable or immutable. If there is a change, make the object mutable
and apply the change. When handing out the collection, make the object immutable (if necessary) before
returning it.
Receiving Mutable Objects
The invoker of a method is interested in the mutability of a returned object for two reasons:
●
It wants to know if it can change the object’s value.
●
It wants to know if the object’s value will change unexpectedly while it has a reference to it.
Use Return Type, Not Introspection
To determine whether it can change a received object, the receiver of a message must rely on the formal type
of the return value. If it receives, for example, an array object typed as immutable, it should not attempt to
mutate it. It is not an acceptable programming practice to determine if an object is mutable based on its class
membership—for example:
if ( [anArray isKindOfClass:[NSMutableArray class]] ) {
// add, remove objects from anArray
}
For reasons related to implementation, what isKindOfClass: returns in this case may not be accurate. But
for reasons other than this, you should not make assumptions about whether an object is mutable based on
class membership. Your decision should be guided solely by what the signature of the method vending the
object says about its mutability. If you are not sure whether an object is mutable or immutable, assume it’s
immutable.
A couple of examples might help clarify why this guideline is important:
Object Mutability
Programming with Mutable Objects
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
64● You read a property list from a file. When the Foundation framework processes the list, it notices that
various subsets of the property list are identical, so it creates a set of objects that it shares among all those
subsets. Afterward you look at the created property list objects and decide to mutate one subset. Suddenly,
and without being aware of it, you’ve changed the tree in multiple places.
● You ask NSView for its subviews (with the subviews method) and it returns an object that is declared to
be an NSArray but which could be an NSMutableArray internally. Then you pass that array to some
other code that, through introspection, determinesit to be mutable and changesit. By changing this array,
the code is mutating internal data structures of the NSView class.
So don’t make an assumption about object mutability based on what introspection tells you about an object.
Treat objects as mutable or not based on what you are handed at the API boundaries (that is, based on the
return type). If you need to unambiguously mark an object as mutable or immutable when you passit to clients,
pass that information as a flag along with the object.
Make Snapshots of Received Objects
If you want to ensure that a supposedly immutable object received from a method does not mutate without
your knowing about it, you can make snapshots of the object by copying it locally. Then occasionally compare
the stored version of the object with the most recent version. If the object has mutated, you can adjust anything
in your program that is dependent on the previous version of the object. Listing 9-2 shows a possible
implementation of this technique.
Listing 9-2 Making a snapshot of a potentially mutable object
static NSArray *snapshot = nil;
- (void)myFunction {
NSArray *thingArray = [otherObj things];
if (snapshot) {
if ( ![thingArray isEqualToArray:snapshot] ) {
[self updateStateWith:thingArray];
}
}
snapshot = [thingArray copy];
}
A problem with making snapshots of objects for later comparison is that it is expensive. You’re required to
make multiple copies of the same object. A more efficient alternative isto use key-value observing. See Key-Value
Observing Programming Guide for a description of this protocol.
Object Mutability
Programming with Mutable Objects
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
65Mutable Objects in Collections
Storing mutable objects in collection objects can cause problems. Certain collections can become invalid or
even corrupt if objects they contain mutate because, by mutating, these objects can affect the way they are
placed in the collection. First, the properties of objects that are keys in hashing collections such as
NSDictionary objects or NSSet objects will, if changed, corrupt the collection if the changed properties
affect the results of the object’s hash or isEqual: methods. (If the hash method of the objectsin the collection
does not depend on their internal state, corruption is less likely.) Second, if an object in an ordered collection
such as a sorted array has its properties changed, this might affect how the object compares to other objects
in the array, thus rendering the ordering invalid.
Object Mutability
Programming with Mutable Objects
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
66An outlet is a property of an object that references another object. The reference is archived through Interface
Builder. The connections between the containing object and its outlets are reestablished every time the
containing object is unarchived from its nib file. The containing object holds an outlet declared as a property
with the type qualifier of IBOutlet and a weak option. For example:
@interface AppController : NSObject
{
}
@property (weak) IBOutlet NSArray *keywords;
Because it is a property, an outlet becomes part of an object’s encapsulated data and is backed by an instance
variable. But an outlet is more than a simple property. The connection between an object and its outlets is
archived in a nib file; when the nib file is loaded, each connection is unarchived and reestablished, and is thus
always available whenever it becomes necessary to send messages to the other object. The type qualifier
IBOutlet is a tag applied to an property declaration so that the Interface Builder application can recognize
the property as an outlet and synchronize the display and connection of it with Xcode.
An outlet is declared as a weak reference (weak) to prevent strong reference cycles.
You create and connect an outlet in the Interface Builder feature of Xcode.The property declaration for the outlet
must be tagged with the IBOutlet qualifier.
An application typically sets outlet connections between its custom controller objects and objects on the user
interface, but they can be made between any objects that can be represented as instances in Interface Builder,
even between two custom objects. As with any item of object state, you should be able to justify its inclusion
in a class; the more outlets an object has, the more memory it takes up. If there are other ways to obtain a
reference to an object, such as finding it through its index position in a matrix, or through its inclusion as a
function parameter, or through use of a tag (an assigned numeric identifier), you should do that instead.
Outlets are a form of object composition, which is a dynamic pattern that requires an object to somehow
acquire referencesto its constituent objectsso that it can send messagesto them. It typically holdsthese other
objects as properties backed by instance variables. These variables must be initialized with the appropriate
references at some point during the execution of the program.
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
67
OutletsThe Receptionist design pattern addresses the general problem of redirecting an event occurring in one
execution context of an application to another execution context for handling. It is a hybrid pattern. Although
it doesn’t appear in the “Gang of Four” book, it combines elements of the Command, Memo, and Proxy design
patterns described in that book. It is also a variant of the Trampoline pattern (which also doesn’t appear in the
book); in this pattern, an event initially is received by a trampoline object, so-called because it immediately
bounces, or redirects, the event to a target object for handling.
The Receptionist Design Pattern in Practice
A KVO notification invokes the observeValueForKeyPath:ofObject:change:context: method
implemented by an observer. If the change to the property occurs on a secondary thread, the
observeValueForKeyPath:ofObject:change:context: code executes on that same thread. There the
central object in this pattern, the receptionist, acts as a thread intermediary. As Figure 11-1 illustrates, a
receptionist object is assigned as the observer of a model object’s property. The receptionist implements
observeValueForKeyPath:ofObject:change:context: to redirect the notification received on a
secondary thread to another execution context—the main operation queue, in this case. When the property
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
68
Receptionist Patternchanges, the receptionist receives a KVO notification. The receptionist immediately adds a block operation to
the main operation queue; the block contains code—specified by the client—that updates the user interface
appropriately.
Figure 11-1 Bouncing KVO updates to the main operation queue
Main
thread
self.value = newValue;
observeValueForKeyPath:
ofObject:change:context:
addOperation:
modelObject
receptionist
Secondary Main operation queue
thread
task
You define a receptionist class so that it has the elements it needs to add itself as an observer of a property
and then convert a KVO notification into an update task. Thus it must know what object it’s observing, the
property of the object that it’s observing, what update task to execute, and what queue to execute it on. Listing
11-1 shows the initial declaration of the RCReceptionist class and its instance variables.
Listing 11-1 Declaring the receptionist class
@interface RCReceptionist : NSObject {
id observedObject;
NSString *observedKeyPath;
RCTaskBlock task;
NSOperationQueue *queue;
}
Receptionist Pattern
The Receptionist Design Pattern in Practice
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
69The RCTaskBlock instance variable is a block object of the following declared type:
typedef void (^RCTaskBlock)(NSString *keyPath, id object, NSDictionary *change);
These parameters are similar to those of the observeValueForKeyPath:ofObject:change:context:
method. Next, the parameter class declares a single class factory method in which an RCTaskBlock object is
a parameter:
+ (id)receptionistForKeyPath:(NSString *)path
object:(id)obj
queue:(NSOperationQueue *)queue
task:(RCTaskBlock)task;
It implementsthis method to assign the passed-in value to instance variables of the created receptionist object
and to add that object as an observer of the model object’s property, as shown in Listing 11-2.
Listing 11-2 The class factory method for creating a receptionist object
+ (id)receptionistForKeyPath:(NSString *)path object:(id)obj queue:(NSOperationQueue
*)queue task:(RCTaskBlock)task {
RCReceptionist *receptionist = [RCReceptionist new];
receptionist->task = [task copy];
receptionist->observedKeyPath = [path copy];
receptionist->observedObject = [obj retain];
receptionist->queue = [queue retain];
[obj addObserver:receptionist forKeyPath:path
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:0];
return [receptionist autorelease];
}
Note that the code copies the block object instead of retaining it. Because the block was probably created on
the stack, it must be copied to the heap so it exists in memory when the KVO notification is delivered.
Finally, the parameter class implements the observeValueForKeyPath:ofObject:change:context:
method. The implementation (see Listing 11-3) is simple.
Receptionist Pattern
The Receptionist Design Pattern in Practice
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
70Listing 11-3 Handling the KVO notification
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
[queue addOperationWithBlock:^{
task(keyPath, object, change);
}];
}
This code simply enqueues the task onto the given operation queue, passing the task block the observed
object, the key path for the changed property, and the dictionary containing the new value. The task is
encapsulated in an NSBlockOperation object that executes the task on the queue.
The client object supplies the block code that updates the user interface when it creates a receptionist object,
as shown in Listing 11-4. Note that when it creates the receptionist object, the client passes in the operation
queue on which the block is to be executed, in this case the main operation queue.
Listing 11-4 Creating a receptionist object
RCReceptionist *receptionist = [RCReceptionist
receptionistForKeyPath:@"value" object:model queue:mainQueue task:^(NSString
*keyPath, id object, NSDictionary *change) {
NSView *viewForModel = [modelToViewMap objectForKey:model];
NSColor *newColor = [change objectForKey:NSKeyValueChangeNewKey];
[[[viewForModel subviews] objectAtIndex:0] setFillColor:newColor];
}];
When to Use the Receptionist Pattern
You can adopt the Receptionist design pattern whenever you need to bounce off work to another execution
context for handling. When you observe a notification, or implement a block handler, or respond to an action
message and you want to ensure that your code executes in the appropriate execution context, you can
implement the Receptionist pattern to redirect the work that must be done to that execution context. With
the Receptionist pattern, you might even perform some filtering or coalescing of the incoming data before
you bounce off a task to processthe data. For example, you could collect data into batches, and then at intervals
dispatch those batches elsewhere for processing.
Receptionist Pattern
When to Use the Receptionist Pattern
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
71One common situation where the Receptionist pattern is useful is key-value observing. In key-value observing,
changes to the value of an model object’s property are communicated to observers via KVO notifications.
However, changes to a model object can occur on a background thread. This results in a thread mismatch,
because changes to a model object’s state typically result in updates to the user interface, and these must
occur on the main thread. In this case, you want to redirect the KVO notifications to the main thread. where
the updates to an application’s user interface can occur.
Receptionist Pattern
When to Use the Receptionist Pattern
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
72Although delegation, bindings, and notification are useful for handling certain forms of communication between
objects in a program, they are not particularly suitable for the most visible sort of communication. A typical
application’s user interface consists of a number of graphical objects, and perhaps the most common of these
objects are controls. A control is a graphical analog of a real-world or logical device (button, slider, checkboxes,
and so on); as with a real-world control, such as a radio tuner, you use it to convey your intent to some system
of which it is a part—that is, an application.
The role of a control on a user interface is simple: It interprets the intent of the user and instructs some other
object to carry out that request. When a user acts on the control by, say, clicking it or pressing the Return key,
the hardware device generates a raw event. The control accepts the event (as appropriately packaged for
Cocoa) and translates it into an instruction that is specific to the application. However, events by themselves
don't give much information about the user's intent; they merely tell you that the user clicked a mouse button
or pressed a key. So some mechanism must be called upon to provide the translation between event and
instruction. This mechanism is called target-action .
Cocoa uses the target-action mechanism for communication between a control and another object. This
mechanism allows the control and, in OS X its cell or cells, to encapsulate the information necessary to send
an application-specific instruction to the appropriate object. The receiving object—typically an instance of a
custom class—is called the target. The action is the message that the control sends to the target. The object
that is interested in the user event—the target—is the one that imparts significance to it, and this significance
is usually reflected in the name it gives to the action.
The Target
A target is a receiver of an action message. A control or, more frequently, its cell holds the target of its action
message as an outlet (see “Outlets” (page 67)). The target usually is an instance of one of your custom classes,
although it can be any Cocoa object whose class implements the appropriate action method.
You can also set a cell’s or control’s target outlet to nil and let the target object be determined at runtime.
When the targetis nil,the application object(NSApplication or UIApplication)searchesfor an appropriate
receiver in a prescribed order:
1. It begins with the first responder in the key window and follows nextResponder links up the responder
chain to the window object’s (NSWindow or UIWindow) content view.
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
73
Target-ActionNote: A key window in OS X responds to key presses for an application and is the receiver of
messages from menus and dialogs. An application’s main window is the principal focus of user
actions and often has key status as well.
2. It tries the window object and then the window object’s delegate.
3. If the main window is different from the key window, it then starts over with the first responder in the
main window and works its way up the main window’s responder chain to the window object and its
delegate.
4. Next, the application object tries to respond. If it can’t respond, it tries its delegate. The application object
and its delegate are the receivers of last resort.
Control objects do not (and should not) retain their targets. However, clients of controlssending action messages
(applications, usually) are responsible for ensuring that their targets are available to receive action messages.
To do this, they may have to retain their targets in memory-managed environments. This precaution applies
equally to delegates and data sources.
The Action
An action is the message a control sends to the target or, from the perspective of the target, the method the
target implements to respond to the action message. A control or—as is frequently the case in AppKit—a
control’s cell stores an action as an instance variable of type SEL. SEL is an Objective-C data type used to
specify the signature of a message. An action message must have a simple, distinct signature. The method it
invokes returns nothing and usually has a sole parameter of type id. This parameter, by convention, is named
sender. Here is an example from the NSResponder class, which defines a number of action methods:
- (void)capitalizeWord:(id)sender;
Action methods declared by some Cocoa classes can also have the equivalent signature:
- (IBAction) deleteRecord:(id)sender;
In this case, IBAction does not designate a data type for a return value; no value is returned. IBAction is a
type qualifier that Interface Builder notices during application development to synchronize actions added
programmatically with its internal list of action methods defined for a project.
Target-Action
The Action
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
74iOS Note: In UIKit, action selectors can also take two other forms. See “Target-Action in UIKit” (page
78) for details.
The senderparameter usually identifies the control sending the action message (although it can be another
object substituted by the actual sender). The idea behind this is similar to a return address on a postcard. The
target can query the sender for more information if it needsto. If the actualsending objectsubstitutes another
object as sender, you should treat that object in the same way. For example, say you have a text field and when
the user enters text, the action method nameEntered: is invoked in the target:
- (void)nameEntered:(id) sender {
NSString *name = [sender stringValue];
if (![name isEqualToString:@""]) {
NSMutableArray *names = [self nameList];
[names addObject:name];
[sender setStringValue:@""];
}
}
Here the responding method extracts the contents of the text field, adds the string to an array cached as an
instance variable, and clears the field. Other possible queries to the sender would be asking an NSMatrix
object for its selected row ([sender selectedRow]), asking an NSButton object for its state ([sender
state]), and asking any cell associated with a control for its tag ([[sender cell] tag]), a tag being a
numeric identifier.
Target-Action in the AppKit Framework
The AppKit framework uses specific architectures and conventions in implementing target-action.
Controls, Cells, and Menu Items
Most controls in AppKit are objects that inherit from the NSControl class. Although a control has the initial
responsibility for sending an action message to its target, it rarely carries the information needed to send the
message. For this, it usually relies on its cell or cells.
A control almost always has one or more cells—objects that inherit from NSCell—associated with it. Why is
there this association? A control is a relatively “heavy” object because it inherits all the combined instance
variables of its ancestors, which include the NSView and NSResponder classes. Because controls are expensive,
Target-Action
Target-Action in the AppKit Framework
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
75cells are used to subdivide the screen real estate of a control into various functional areas. Cells are lightweight
objects that can be thought of as overlaying all or part of the control. But it's not only a division of area, it's a
division of labor. Cells do some of the drawing that controls would otherwise have to do, and cells hold some
of the data that controls would otherwise have to carry. Two items of this data are the instance variables for
target and action. Figure 12-1 (page 76) depicts the control-cell architecture.
Being abstract classes, NSControl and NSCell both incompletely handle the setting of the target and action
instance variables. By default, NSControl simply sets the information in its associated cell, if one exists.
(NSControl itself supports only a one-to-one mapping between itself and a cell; subclasses of NSControl
such as NSMatrix support multiple cells.) In its default implementation, NSCell simply raises an exception.
You must go one step further down the inheritance chain to find the class that really implements the setting
of target and action: NSActionCell.
Objects derived from NSActionCell provide target and action values to their controls so the controls can
compose and send an action message to the proper receiver. An NSActionCell object handles mouse (cursor)
tracking by highlighting its area and assisting its control in sending action messages to the specified target.
In most cases, the responsibility for an NSControl object’s appearance and behavior is completely given over
to a corresponding NSActionCell object. (NSMatrix, and itssubclass NSForm, are subclasses of NSControl
that don’t follow this rule.)
Figure 12-1 How the target-action mechanism works in the control-cell architecture
washerObject
dryerObject
washerCell
dryerCell
Target
Action
Target
Action
Control
(NSMatrix)
Cells
(NSButtonCell)
(void)dryIt: (id)sender
(void)washIt: (id)sender
Target-Action
Target-Action in the AppKit Framework
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
76When users choose an item from a menu, an action is sent to a target. Yet menus (NSMenu objects) and their
items (NSMenuItem objects) are completely separate, in an architectural sense, from controls and cells. The
NSMenuItem class implements the target-action mechanism for its own instances; an NSMenuItem object has
both target and action instance variables (and related accessor methods) and sends the action message to the
target when a user chooses it.
Note: See Control and Cell Programming Topics for Cocoa and Application Menu and Pop-up List
Programming Topics for more information about the control-cell architecture.
Setting the Target and Action
You can set the targets and actions of cells and controls programmatically or by using Interface Builder. For
most developers and mostsituations, Interface Builder isthe preferred approach. When you use it to set controls
and targets, Interface Builder provides visual confirmation, allows you to lock the connections, and archives
the connections to a nib file. The procedure is simple:
1. Declare an action method in the header file of your custom class that has the IBAction qualifier.
2. In Interface Builder, connect the control sending the message to the action method of the target.
If the action is handled by a superclass of your custom class or by an off-the-shelf AppKit or UIKit class, you
can make the connection without declaring any action method. Of course, if you declare an action method
yourself, you must be sure to implement it.
To set the action and the target programmatically, use the following methods to send messages to a control
or cell object:
- (void)setTarget:(id)anObject;
- (void)setAction:(SEL)aSelector;
The following example shows how you might use these methods:
[aCell setTarget:myController];
[aControl setAction:@selector(deleteRecord:)];
[aMenuItem setAction:@selector(showGuides:)];
Target-Action
Target-Action in the AppKit Framework
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
77Programmatically setting the target and action does have its advantages and in certain situations it is the only
possible approach. For example, you might want the target or action to vary according to some runtime
condition, such as whether a network connection exists or whether an inspector window has been loaded.
Another example is when you are dynamically populating the items of a pop-up menu, and you want each
pop-up item to have its own action.
Actions Defined by AppKit
The AppKit framework not only includes many NSActionCell-based controls for sending action messages,
it defines action methods in many of its classes. Some of these actions are connected to default targets when
you create a Cocoa application project. For example, the Quit command in the application menu is connected
to the terminate: method in the global application object (NSApp).
The NSResponder class also defines many default action messages (also known as standard commands) for
common operations on text. This allowsthe Cocoa textsystem to send these action messages up an application’s
responder chain—a hierarchical sequence of event-handling objects—where it can be handled by the first
NSView, NSWindow, or NSApplication object that implements the corresponding method.
Target-Action in UIKit
The UIKit framework also declares and implements a suite of control classes; the control classesin thisframework
inherit from the UIControl class, which defines most of the target-action mechanism for iOS. However there
are some fundamental differences in how the AppKit and UIKit frameworks implement target-action. One of
these differences is that UIKit does not have any true cell classes. Controls in UIKit do not rely upon their cells
for target and action information.
A larger difference in how the two frameworks implement target-action lies in the nature of the event model.
In the AppKit framework, the user typically uses a mouse and keyboard to register events for handling by the
system. These events—such as clicking on a button—are limited and discrete. Consequently, a control object
in AppKit usually recognizes a single physical event as the trigger for the action it sends to its target. (In the
case of buttons, this is a mouse-up event.) In iOS, the user’s fingers are what originate events instead of mouse
clicks, mouse drags, or physical keystrokes. There can be more than one finger touching an object on the screen
at one time, and these touches can even be going in different directions.
To account for this multitouch event model, UIKit declares a set of control-event constants in UIControl.h
that specify various physical gestures that users can make on controls, such as lifting a finger from a control,
dragging a finger into a control, and touching down within a text field. You can configure a control object so
that it responds to one or more of these touch events by sending an action message to a target. Many of the
Target-Action
Target-Action in UIKit
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
78control classes in UIKit are implemented to generate certain control events; for example, instances of the
UISlider class generate a UIControlEventValueChanged control event, which you can use to send an
action message to a target object.
You set up a control so that it sends an action message to a target object by associating both target and action
with one or more control events. To do this, send addTarget:action:forControlEvents: to the control
for each target-action pair you want to specify. When the user touches the control in a designated fashion, the
control forwards the action message to the global UIApplication object in a
sendAction:to:from:forEvent: message. As in AppKit, the global application object is the centralized
dispatch point for action messages. If the control specifies a nil target for an action message, the application
queries objects in the responder chain until it finds one that is willing to handle the action message—that is,
one implementing a method corresponding to the action selector.
In contrast to the AppKit framework, where an action method may have only one or perhapstwo valid signatures,
the UIKit framework allows three different forms of action selector:
- (void)action
- (void)action:(id)sender
- (void)action:(id)sender forEvent:(UIEvent *)event
To learn more about the target-action mechanism in UIKit, read UIControl Class Reference .
Target-Action
Target-Action in UIKit
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
79There are a number of data types in the Core Foundation framework and the Foundation framework that can
be used interchangeably. This capability, called toll-free bridging , means that you can use the same data type
as the parameter to a Core Foundation function call or as the receiver of an Objective-C message. For example,
NSLocale (see NSLocale Class Reference ) is interchangeable with its Core Foundation counterpart, CFLocale
(see CFLocale Reference ). Therefore, in a method where you see an NSLocale * parameter, you can pass a
CFLocaleRef, and in a function where you see a CFLocaleRef parameter, you can pass an NSLocale
instance. You cast one type to the other to suppress compiler warnings, as illustrated in the following example.
NSLocale *gbNSLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"];
CFLocaleRef gbCFLocale = (CFLocaleRef) gbNSLocale;
CFStringRef cfIdentifier = CFLocaleGetIdentifier (gbCFLocale);
NSLog(@"cfIdentifier: %@", (NSString *)cfIdentifier);
// logs: "cfIdentifier: en_GB"
CFRelease((CFLocaleRef) gbNSLocale);
CFLocaleRef myCFLocale = CFLocaleCopyCurrent();
NSLocale * myNSLocale = (NSLocale *) myCFLocale;
[myNSLocale autorelease];
NSString *nsIdentifier = [myNSLocale localeIdentifier];
CFShow((CFStringRef) [@"nsIdentifier: " stringByAppendingString:nsIdentifier]);
// logs identifier for current locale
Note from the example that the memory management functions and methods are also interchangeable—you
can use CFRelease with a Cocoa object and release and autorelease with a Core Foundation object.
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
80
Toll-Free BridgingNote: When using garbage collection, there are important differencesto how memory management
works for Cocoa objects and Core Foundation objects. See “Using Core Foundation with Garbage
Collection” for details.
Toll-free bridging has been available since OS X v10.0. Table 13-1 provides a list of the data types that are
interchangeable between Core Foundation and Foundation. For each pair, the table also lists the version of
OS X in which toll-free bridging between them became available.
Table 13-1 Data types that can be used interchangeably between Core Foundation and Foundation
Core Foundation type Foundation class Availability
CFArrayRef NSArray OS X v10.0
CFAttributedStringRef NSAttributedString OS X v10.4
CFCalendarRef NSCalendar OS X v10.4
CFCharacterSetRef NSCharacterSet OS X v10.0
CFDataRef NSData OS X v10.0
CFDateRef NSDate OS X v10.0
CFDictionaryRef NSDictionary OS X v10.0
CFErrorRef NSError OS X v10.5
CFLocaleRef NSLocale OS X v10.4
CFMutableArrayRef NSMutableArray OS X v10.0
CFMutableAttributedStringRef NSMutableAttributedString OS X v10.4
CFMutableCharacterSetRef NSMutableCharacterSet OS X v10.0
CFMutableDataRef NSMutableData OS X v10.0
CFMutableDictionaryRef NSMutableDictionary OS X v10.0
CFMutableSetRef NSMutableSet OS X v10.0
CFMutableStringRef NSMutableString OS X v10.0
CFNumberRef NSNumber OS X v10.0
CFReadStreamRef NSInputStream OS X v10.0
Toll-Free Bridging
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
81Core Foundation type Foundation class Availability
CFRunLoopTimerRef NSTimer OS X v10.0
CFSetRef NSSet OS X v10.0
CFStringRef NSString OS X v10.0
CFTimeZoneRef NSTimeZone OS X v10.0
CFURLRef NSURL OS X v10.0
CFWriteStreamRef NSOutputStream OS X v10.0
Note: Not all data types are toll-free bridged, even though their names might suggest that they
are. For example, NSRunLoop is not toll-free bridged to CFRunLoop, NSBundle is not toll-free bridged
to CFBundle, and NSDateFormatter is not toll-free bridged to CFDateFormatter.
Toll-Free Bridging
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
82This table describes the changes to Concepts in Objective-C Programming .
Date Notes
Descriptions of design patterns, architectures, and other concepts
important in Cocoa and Cocoa Touch development.
2012-01-09
2012-01-09 | © 2012 Apple Inc. All Rights Reserved.
83
Document Revision HistoryApple Inc.
© 2012 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Cocoa, Cocoa Touch,
Objective-C, OS X, and Xcode are trademarks of
Apple Inc., registered in the U.S. and other
countries.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
Local and Push
Notification
Programming GuideContents
About Local Notifications and Push Notifications 5
At a Glance 6
The Problem That Local and Push Notifications Solve 6
Local and Push Notifications Are Different in Origination 6
You Schedule a Local Notification, Register a Push Notification, and Handle Both 6
The Apple Push Notification Service Is the Gateway for Push Notifications 7
You Must Obtain Security Credentials for Push Notifications 7
The Provider Communicates with APNs over a Binary Interface 7
Prerequisites 8
See Also 8
Local and Push Notifications in Depth 9
Push and Local Notifications Appear the Same to Users 9
More About Local Notifications 12
More About Push Notifications 13
Scheduling, Registering, and Handling Notifications 15
Preparing Custom Alert Sounds 15
Scheduling Local Notifications 16
Registering for Remote Notifications 19
Handling Local and Remote Notifications 21
Passing the Provider the Current Language Preference (Remote Notifications) 26
Apple Push Notification Service 28
A Push Notification and Its Path 28
Feedback Service 29
Quality of Service 30
Security Architecture 30
Service-to-Device Connection Trust 31
Provider-to-Service Connection Trust 31
Token Generation and Dispersal 32
Token Trust (Notification) 34
Trust Components 34
The Notification Payload 35
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
2Localized Formatted Strings 37
Examples of JSON Payloads 39
Provisioning and Development 42
Sandbox and Production Environments 42
Provisioning Procedures 43
Creating the SSL Certificate and Keys 43
Creating and Installing the Provisioning Profile 44
Installing the SSL Certificate and Key on the Server 45
Provider Communication with Apple Push Notification Service 47
General Provider Requirements 47
The Binary Interface and Notification Formats 48
The Feedback Service 53
Document Revision History 55
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
3
ContentsFigures, Tables, and Listings
Local and Push Notifications in Depth 9
Figure 1-1 A notification alert 10
Figure 1-2 An application icon with a badge number (iOS) 11
Figure 1-3 A notification alert message with the action button suppressed 11
Scheduling, Registering, and Handling Notifications 15
Listing 2-1 Creating, configuring, and scheduling a local notification 17
Listing 2-2 Presenting a local notification immediately while running in the background 18
Listing 2-3 Registering for remote notifications 21
Listing 2-4 Handling a local notification when an application is launched 23
Listing 2-5 Downloading data from a provider 24
Listing 2-6 Handling a local notification when an application is already running 25
Listing 2-7 Getting the current supported language and sending it to the provider 26
Apple Push Notification Service 28
Figure 3-1 A push notification from a provider to a client application 29
Figure 3-2 Push notifications from multiple providers to multiple devices 29
Figure 3-3 Sharing the device token 33
Table 3-1 Keys and values of the aps dictionary 36
Table 3-2 Child properties of the alert property 36
Provider Communication with Apple Push Notification Service 47
Figure 5-1 Simple notification format 49
Figure 5-2 Enhanced notification format 50
Figure 5-3 Format of error-response packet 51
Figure 5-4 Binary format of a feedback tuple 54
Table 5-1 Codes in error-response packet 51
Listing 5-1 Sending a notification in the simple format via the binary interface 49
Listing 5-2 Sending a notification in the enhanced format via the binary interface 52
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
4Local notifications and push notifications are ways for an application that isn’t running in the foreground to
let its users know it has information for them. The information could be a message, an impending calendar
event, or new data on a remote server. When presented by the operating system, local and push notifications
look and sound the same. They can display an alert message or they can badge the application icon. They can
also play a sound when the alert or badge number is shown.
Push notifications were introduced in iOS 3.0 and in OS X version 10.7. Local notifications were introduced in
iOS 4.0; they are not available in OS X.
When users are notified that the application has a message, event, or other data for them, they can launch the
application and see the details. They can also choose to ignore the notification, in which case the application
is not activated.
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
5
About Local Notifications and Push NotificationsNote: Push notifications and local notifications are not related to broadcast notifications
(NSNotificationCenter) or key-value observing notifications.
At a Glance
Local notifications and push notifications have several important aspects you should be aware of.
The Problem That Local and Push Notifications Solve
Only one application can be active in the foreground at any time. Many applications operate in a time-based
or interconnected environment where events of interest to users can occur when the application is not in the
foreground. Local and push notifications allow these applicationsto notify their users when these events occur.
Relevant Chapter: “Local and Push Notifications in Depth” (page 9)
Local and Push Notifications Are Different in Origination
Local and push notifications serve different design needs. A local notification is local to an application on an
iPhone, iPad, or iPod touch. Push notifications—also known as remote notifications—arrive from outside a
device. They originate on a remote server—the application’s provider—and are pushed to applications on
devices (via the Apple Push Notification service) when there are messages to see or data to download.
Relevant Chapter: “Local and Push Notifications in Depth” (page 9)
You Schedule a Local Notification, Register a Push Notification, and Handle Both
To have iOS deliver a local notification at a later time, an application creates a UILocalNotification object,
assignsit a delivery date and time,specifies presentation details, and schedulesit. To receive push notifications,
an application must register to receive the notifications and then pass to its provider a device token it gets
from the operating system.
When the operating system delivers a local notification (iOS only) or push notification (iOS or OS X) and the
target application is not running in the foreground, it presents the notification (alert, icon badge number,
sound). If there is a notification alert and the user taps or clicks the action button (or moves the action slider),
the application launches and calls a method to pass in the local-notification object or remote-notification
payload. If the application is running in the foreground when the notification is delivered, the application
delegate receives a local or push notification.
About Local Notifications and Push Notifications
At a Glance
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
6Relevant Chapter: “Scheduling, Registering, and Handling Notifications” (page 15)
The Apple Push Notification Service Is the Gateway for Push Notifications
Apple Push Notification service (APNs) propagates push notificationsto devices having applicationsregistered
to receive those notifications. Each device establishes an accredited and encrypted IP connection with the
service and receives notifications over this persistent connection. Providers connect with APNs through a
persistent and secure channel while monitoring incoming data intended for their client applications. When
new data for an application arrives, the provider prepares and sends a notification through the channel to
APNs, which pushes the notification to the target device.
Related Chapter: “Apple Push Notification Service” (page 28)
You Must Obtain Security Credentials for Push Notifications
To develop and deploy the provider side of an application for push notifications, you must get SSL certificates
from the appropriate Dev Center. Each certificate is limited to a single application, identified by its bundle ID;
it is also limited to one of two environments, sandbox (for development and testing) and production. These
environments have their own assigned IP address and require their own certificates. You must also obtain
provisioning profiles for each of these environments.
Related Chapter: “Provisioning and Development” (page 42)
The Provider Communicates with APNs over a Binary Interface
The binary interface is asynchronous and uses a streaming TCP socket design for sending push notifications
as binary content to APNs. There is a separate interface for the sandbox and production environments, each
with its own address and port. For each interface, you need to use TLS (or SSL) and the SSL certificate you
obtained to establish a secured communications channel. The provider composes each outgoing notification
and sends it over this channel to APNs.
APNs has a feedback service that maintains a per-application list of devicesfor which there were failed-delivery
attempts (that is, APNs was unable to deliver a push notification to an application on a device). Periodically,
the provider should connect with the feedback service to see what devices have persistent failures so that it
can refrain from sending push notifications to them.
About Local Notifications and Push Notifications
At a Glance
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
7Related Chapters: “Apple Push Notification Service” (page 28), “Provider Communication with
Apple Push Notification Service” (page 47)
Prerequisites
For local notifications and the client-side implementation of push notifications, familiarity with application
development for iOS is assumed. For the provider side of the implementation, knowledge of TLS/SSL and
streaming sockets is helpful.
See Also
You might find these additional sources of information useful for understanding and implementing local and
push notifications :
● The reference documentation for UILocalNotification, UIApplication, and
UIApplicationDelegate describe the local- and push-notification API for client applications in iOS.
● The reference documentation for NSApplication and NSApplicationDelegate Protocol describe
the push-notification API for client applications in OS X.
● Security Overview describes the security technologies and techniques used for the iOS and Macs.
● RFC 5246 is the standard for the TLS protocol.
Secure communication between data providers and Apple Push Notification Service requires knowledge
of Transport Layer Security (TLS) or its predecessor, Secure Sockets Layer (SSL). Refer to one of the many
online or printed descriptions of these cryptographic protocols for further information.
About Local Notifications and Push Notifications
Prerequisites
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
8The essential purpose of both local and push notifications is to enable an application to inform its users that
it hassomething for them—for example, a message or an upcoming appointment—when the application isn’t
running in the foreground. The essential difference between local notifications and push notificationsissimple:
● Local notifications are scheduled by an application and delivered by iOS on the same device.
Local notifications are available in iOS only.
● Push notifications, also known as remote notifications, are sent by an application’s remote server (its
provider) to Apple Push Notification service, which pushes the notification to devices on which the
application is installed.
Push notifications are available in both iOS and, beginning with OS X v10.7 (Lion), OS X.
The following sections describe what local and push notifications have in common and then examine their
differences.
Note: For usage guidelines for push and local notifications in iOS, see “Enabling Push Notifications”
in iOS Human Interface Guidelines.
Push and Local Notifications Appear the Same to Users
From a user’s perspective, a push notification and a local notification appear to be the same thing. But that’s
because the purpose is the same: to notify users of an application—which might not currently be running in
the foreground—that there is something of interest for them.
Let’s say you’re using your iPhone—making phone calls, surfing the Internet, listening to music. You have a
chess application installed on your iPhone, and you decide to start a game with a friend who is playing remotely.
You make the first move (which is duly noted by the game’s provider), and then quit the client application to
read some email. In the meantime, your friend counters your move. The provider for the chess application
learns about this move and, seeing that the chess application on your device is no longer connected, sends a
push notification to Apple Push Notification service (APNs).
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
9
Local and Push Notifications in DepthAlmost immediately, your device—or more precisely, the operating system on your device—receives the
notification over the Wi-Fi or cellular connection from APNs. Because your chess application is not currently
running, iOS displays an alert similar to Figure 1-1. The message consists of the application name, a short
message, and (in this case) two buttons: Close and View. The button on the right is called the action button
and its default title is“View”. An application can customize the title of the action button and can internationalize
the button title and the message so that they are in the user’s preferred language.
Figure 1-1 A notification alert
If you tap the View button, the chess application launches, connects with its provider, downloads the new
data, and adjuststhe chessboard user interface to show your friend’s move. (Pressing Close dismissesthe alert.)
OS X Note: Currently, the only type of push notification in OS X for non-running applications is icon
badging. In other words, an application’s icon in the Dock is badged only if the application isn’t
running. If users have not already placed the icon in the Dock, the system inserts the icon into the
Dock so that it can badge it (and removes it after the application next terminates). Running
applications may examine the notification payload for other types of notifications(alerts and sounds)
and handle them appropriately.
Let’s consider a type of application with another requirement. This application manages a to-do list, and each
item in the list has a date and time when the item must be completed. The user can request the application
to notify it at a specific interval before this due date expires. To effect this, the application schedules a local
notification for that date and time. Instead of specifying an alert message, this time the application chooses
to specify a badge number (1). At the appointed time, iOS displays a badge number in the upper-right corner
of the icon of the application, such as illustrated in Figure 1-2.
Local and Push Notifications in Depth
Push and Local Notifications Appear the Same to Users
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
10For both local and push notifications, the badge number is specific to an application and can indicate any
number of things,such asthe number of impending calendar events or the number of data itemsto download
or the number of unread (but already downloaded) email messages. The user sees the badge and taps the
application icon—or, in OS X, clicks the icon in the dock—to launch the application, which then displays the
to-do item or whatever else is of interest to the user.
Figure 1-2 An application icon with a badge number (iOS)
In iOS, an application can specify a sound file along with an alert message or badge number. The sound file
should contain a short, distinctive sound. At the same moment iOS displays the alert or badges the icon, it
plays the sound to alert the user to the incoming notification.
Notification alert message can have one button instead of two. In the latter case, the action button issuppressed,
as illustrated in Figure 1-3. The user can only dismiss these kinds of alerts.
Figure 1-3 A notification alert message with the action button suppressed
Local and Push Notifications in Depth
Push and Local Notifications Appear the Same to Users
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
11The operating system delivers a local or push notification to an application whether the application is running
or not. If the application is running when the notification arrives, no alert is displayed or icon badged or sound
played, even if (in iOS) the device screen islocked. Instead, the application delegate isinformed of the notification
and can handle it directly. (“Scheduling, Registering, and Handling Notifications” (page 15) discusses the
various delivery scenarios in detail.)
Users of iPhone, iPad, and iPod touch devices can control whether the device or specific applications installed
on the device should receive push notifications. They can also selectively enable or disable push notification
types (that is, icon badging, alert messages, and sounds) for specific applications. They set these restrictions
in the Notifications preference of the Settings application. The UIKit framework provides a programming
interface to detect this user preference for a given application.
More About Local Notifications
Local notifications(available only in iOS) are ideally suited for applications with time-based behaviors, including
simple calendar or to-do list applications. Applicationsthat run in the background for the limited period allowed
by iOS might also find local notifications useful. For example, applicationsthat depend on serversfor messages
or data can poll their servers for incoming items while running in the background; if a message is ready to
view or an update is ready to download, they can then present a local notification immediately to inform their
users.
A local notification is an instance of UILocalNotification with three general kinds of properties:
● Scheduled time. You must specify the date and time the operating system delivers the notification; this
is known as the fire date . You may qualify the fire date with a specific time zone so that the system can
make adjustments to the fire date when the user travels. You can also request the operating system to
reschedule the notification on some regular interval (weekly, monthly, and so on).
● Notification type. This category includes the alert message, the title of the action button, the application
icon badge number, and a sound to play.
● Custom data. Local notifications can include a dictionary of custom data.
“Scheduling Local Notifications” (page 16) describesthese propertiesin programmatic detail.Once an application
has created a local-notification object, it can either schedule it with the operating system or present it
immediately.
Each application on a device is limited to the soonest-firing 64 scheduled local notifications. The operating
system discards notificationsthat exceed thislimit. It considers a recurring notification to be a single notification.
Local and Push Notifications in Depth
More About Local Notifications
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
12More About Push Notifications
An iOS application or a Mac app is often only a part of a larger application based on the client/server model.
The client side of the application is installed on the device or computer; the server side of the application has
the main function of providing data to its many client applications. (Hence it is termed a provider.) A client
application occasionally connects with its provider and downloads the data that is waiting for it. Email and
social-networking applications are examples of this client/server model.
But what if the application is not connected to its provider or even running on the device or computer when
the provider has new data for it to download? How does it learn about this waiting data? Push notifications
are the solution to this dilemma. A push notification is a short message that a provider has delivered to the
operating system of a device or computer; the operating system, in turn, informsthe user of a client application
that there is data to be downloaded, a message to be viewed, and so on. If the user enables this feature (on
iOS) and the application is properly registered, the notification is delivered to the operating system and possibly
to the application. Apple Push Notification service is the primary technology for the push-notification feature.
Push notificationsserve much the same purpose as a background application on a desktop system, but without
the additional overhead. For an application that is not currently running—or, in the case of iOS, not running
in the foreground—the notification occurs indirectly. The operating system receives a push notification on
behalf of the application and alerts the user. Once alerted, users may choose to launch the application, which
then downloads the data from its provider. If an application is running when a notification comes in, the
application can choose to handle the notification directly.
iOS Note: Beginning with iOS 4.0, applications can run in the background, but only for a limited
period. Only one application may be executing in the foreground at a time.
As its name suggests, Apple Push Notification service (APNs) uses a push design to deliver notifications to
devices and computers. A push design differs from its opposite, a pull design, in that the immediate recipient
of the notification—in this case, the operating system—passively listensfor updatesrather than actively polling
for them. A push design makes possible a wide and timely dissemination of information with few of the
scalability problems inherent with pull designs. APNs uses a persistent IP connection for implementing push
notifications.
Most of a push notification consists of a payload: a property list containing APNs-defined propertiesspecifying
how the user is to be notified. For performance reasons, the payload is deliberately small. Although you may
define custom properties for the payload, you should never use the remote-notification mechanism for data
transport because delivery of push notificationsis not guaranteed. For more on the payload,see “The Notification
Payload” (page 35).
Local and Push Notifications in Depth
More About Push Notifications
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
13APNs retains the last notification it receives from a provider for an application on a device; so, if a device or
computer comes online and has not received the notification, APNs pushes the stored notification to it. A
device running iOS receives push notifications over both Wi-Fi and cellular connections; a computer running
OS X receives push notifications over both WiFi and Ethernet connections.
Important: In iOS, Wi-Fi is used for push notifications only if there is no cellular connection or if the device
is an iPod touch. For some devices to receive notifications via Wi-Fi, the device’s display must be on (that
is, it cannot be sleeping) or it must be plugged in. The iPad, on the other hand, remains associated with
the Wi-Fi access point while asleep, thus permitting the delivery of push notifications. The Wi-Fi radio wakes
the host processor for any incoming traffic.
Adding the remote-notification feature to your application requires that you obtain the proper certificates
from the Dev Center for either iOS or OS X and then write the requisite code for the client and provider sides
of the application. “Provisioning and Development” (page 42) explains the provisioning and setup steps, and
“Provider Communication with Apple Push Notification Service” (page 47) and “Scheduling, Registering, and
Handling Notifications” (page 15) describe the details of implementation.
Apple Push Notification service continually monitors providersfor irregular behavior, looking forsudden spikes
of activity, rapid connect-disconnect cycles, and similar activity. Apple seeksto notify providers when it detects
this behavior, and if the behavior continues, it may put the provider’s certificate on a revocation list and refuse
further connections. Any continued irregular or problematic behavior may result in the termination of a
provider's access to APNs.
Local and Push Notifications in Depth
More About Push Notifications
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
14This chapter describes the tasks that a iPhone, iPad, or iPod touch application should (or might) do to schedule
local notifications, register remote notifications, and handle both local and remote notifications. Because the
client-side API for push notifications refers to push notifications as remote notifications, that terminology is
used in this chapter.
Preparing Custom Alert Sounds
For remote notifications in iOS, you can specify a custom sound that iOS plays when it presents a local or
remote notification for an application. The sound files must be in the main bundle of the client application.
Because custom alert sounds are played by the iOS system-sound facility, they must be in one of the following
audio data formats:
● Linear PCM
● MA4 (IMA/ADPCM)
● µLaw
● aLaw
You can package the audio data in an aiff, wav, or caf file. Then, in Xcode, add the sound file to your project
as a nonlocalized resource of the application bundle.
You may use the afconvert tool to convert sounds. For example, to convert the 16-bit linear PCM system
sound Submarine.aiff to IMA4 audio in a CAF file, use the following command in the Terminal application:
afconvert /System/Library/Sounds/Submarine.aiff ~/Desktop/sub.caf -d ima4 -f caff
-v
You can inspect a sound to determine its data format by opening it in QuickTime Player and choosing Show
Movie Inspector from the Movie menu.
Custom sounds must be under 30 seconds when played. If a custom sound is over that limit, the defaultsystem
sound is played instead.
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
15
Scheduling, Registering, and Handling NotificationsScheduling Local Notifications
Creating and scheduling local notifications in iOS requires that you perform a few simple steps:
1. Allocate and initialize a UILocalNotification object.
2. Set the date and time that the operating system should deliver the notification. This is the fireDate
property.
If you set the timeZone property to the NSTimeZone object for the current locale, the system automatically
adjusts the fire date when the device travels across (and is reset for) different time zones. (Time zones
affect the values of date components—that is, day, month, hour, year, and minute—that the system
calculates for a given calendar and date value.) You can also schedule the notification for delivery on a
recurring basis (daily, weekly, monthly, and so on).
3. Configure the substance of the notification: alert, icon badge number, and sound.
● The alert has a property for the message (the alertBody property) and for the title of the action
button or slider (alertAction); both of these string values can be internationalized for the user’s
current language preference.
● You set the badge number to display on the application icon through the
applicationIconBadgeNumber property.
● You can assign the filename of a nonlocalized custom sound in the application’s main bundle to the
soundName property; to get the default system sound, assign
UILocalNotificationDefaultSoundName. Sounds should always accompany an alert message
or icon badging; they should not be played otherwise.
4. Optionally, you can attach custom data to the notification through the userInfo property.
Keys and values in the userInfo dictionary must be property-list objects.
5. Schedule the local notification for delivery.
You schedule a local notification by calling the UIApplicationmethod scheduleLocalNotification:.
The application uses the fire date specified in the UILocalNotification object for the moment of
delivery. Alternatively, you can present the notification immediately by calling the
presentLocalNotificationNow: method.
The method in Listing 2-1 creates and schedules a notification to inform the user of a hypothetical to-do list
application about the impending due date of a to-do item. There are a couple things to note about it. For the
alertBody and alertAction properties, it fetches from the main bundle (via the NSLocalizedString
macro) strings localized to the user’s preferred language. It also adds the name of the relevant to-do item to
a dictionary assigned to the userInfo property.
Scheduling, Registering, and Handling Notifications
Scheduling Local Notifications
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
16Listing 2-1 Creating, configuring, and scheduling a local notification
- (void)scheduleNotificationWithItem:(ToDoItem *)item interval:(int)minutesBefore
{
NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
NSDateComponents *dateComps = [[NSDateComponents alloc] init];
[dateComps setDay:item.day];
[dateComps setMonth:item.month];
[dateComps setYear:item.year];
[dateComps setHour:item.hour];
[dateComps setMinute:item.minute];
NSDate *itemDate = [calendar dateFromComponents:dateComps];
[dateComps release];
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
if (localNotif == nil)
return;
localNotif.fireDate = [itemDate addTimeInterval:-(minutesBefore*60)];
localNotif.timeZone = [NSTimeZone defaultTimeZone];
localNotif.alertBody = [NSString stringWithFormat:NSLocalizedString(@"%@ in
%i minutes.", nil),
item.eventName, minutesBefore];
localNotif.alertAction = NSLocalizedString(@"View Details", nil);
localNotif.soundName = UILocalNotificationDefaultSoundName;
localNotif.applicationIconBadgeNumber = 1;
NSDictionary *infoDict = [NSDictionary dictionaryWithObject:item.eventName
forKey:ToDoItemKey];
localNotif.userInfo = infoDict;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
[localNotif release];
}
Scheduling, Registering, and Handling Notifications
Scheduling Local Notifications
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
17You can cancel a specific scheduled notification by calling cancelLocalNotification: on the application
object, and you can cancel all scheduled notifications by calling cancelAllLocalNotifications. Both of
these methods also programmatically dismiss a currently displayed notification alert.
Applications might also find local notifications useful when they run in the background and some message,
data, or other item arrivesthat might be of interest to the user. In this case, they should present the notification
immediately using the UIApplication method presentLocalNotificationNow: (iOS gives an application
a limited time to run in the background). Listing 2-2 illustrates how you might do this.
Listing 2-2 Presenting a local notification immediately while running in the background
- (void)applicationDidEnterBackground:(UIApplication *)application {
NSLog(@"Application entered background state.");
// bgTask is instance variable
NSAssert(self->bgTask == UIInvalidBackgroundTask, nil);
bgTask = [application beginBackgroundTaskWithExpirationHandler: ^{
dispatch_async(dispatch_get_main_queue(), ^{
[application endBackgroundTask:self->bgTask];
self->bgTask = UIInvalidBackgroundTask;
});
}];
dispatch_async(dispatch_get_main_queue(), ^{
while ([application backgroundTimeRemaining] > 1.0) {
NSString *friend = [self checkForIncomingChat];
if (friend) {
UILocalNotification *localNotif = [[UILocalNotification alloc]
init];
if (localNotif) {
localNotif.alertBody = [NSString stringWithFormat:
NSLocalizedString(@"%@ has a message for you.", nil),
friend];
localNotif.alertAction = NSLocalizedString(@"Read Message",
nil);
localNotif.soundName = @"alarmsound.caf";
localNotif.applicationIconBadgeNumber = 1;
[application presentLocalNotificationNow:localNotif];
Scheduling, Registering, and Handling Notifications
Scheduling Local Notifications
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
18[localNotif release];
friend = nil;
break;
}
}
}
[application endBackgroundTask:self->bgTask];
self->bgTask = UIInvalidBackgroundTask;
});
}
Registering for Remote Notifications
An application must register with Apple Push Notification service for the operating systems on a device and
on a computer to receive remote notifications sent by the application’s provider. Registration has three stages:
1. The application calls the registerForRemoteNotificationTypes: method.
2. The delegate implements the
application:didRegisterForRemoteNotificationsWithDeviceToken: method to receive the
device token.
3. It passes the device token to its provider as a non-object, binary value.
Note: Unless otherwise noted, all methods cited in thissection are declared with identicalsignatures
by both UIApplication and NSApplication, and, for delegates, by both
NSApplicationDelegate Protocol and UIApplicationDelegate.
What happens between the application, the device, Apple Push Notification Service, and the provider during
this sequence is illustrated by Figure 3-3 in “Token Generation and Dispersal” (page 32).
An application should register every time it launches and give its provider the current token. It calls
theregisterForRemoteNotificationTypes: method to kick off the registration process. The parameter
of this method takes a UIRemoteNotificationType (or, for OS X, a NSRemoteNotificationType) bit
mask that specifies the initial types of notifications that the application wishes to receive—for example,
icon-badging and sounds, but not alert messages. In iOS, users can thereafter modify the enabled notification
types in the Notifications preference of the Settings application. In both iOS and OS X, you can retrieve the
Scheduling, Registering, and Handling Notifications
Registering for Remote Notifications
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
19currently enabled notification types by calling the enabledRemoteNotificationTypes method. The
operating system does not badge icons, display alert messages, or play alertsoundsif any of these notifications
types are not enabled, even if they are specified in the notification payload.
OS XNote: Because the only notification type supported for non-running applicationsisicon-badging,
simply pass NSRemoteNotificationTypeBadge as the parameter of
registerForRemoteNotificationTypes:.
If registration issuccessful, APNsreturns a device token to the device and iOS passesthe token to the application
delegate in the application:didRegisterForRemoteNotificationsWithDeviceToken: method. The
application should connect with its provider and pass it this token, encoded in binary format. If there is a
problem in obtaining the token, the operating system informs the delegate by calling the
application:didFailToRegisterForRemoteNotificationsWithError:method. The NSError object
passed into this method clearly describes the cause of the error. The error might be, for instance, an erroneous
aps-environment value in the provisioning profile. You should view the error as a transient state and not
attempt to parse it. (See “Creating and Installing the Provisioning Profile” (page 44) for details.)
iOS Note: If a cellular or Wi-Fi connection is not available, neither the
application:didRegisterForRemoteNotificationsWithDeviceToken: method or the
application:didFailToRegisterForRemoteNotificationsWithError: method is called.
For Wi-Fi connections, this sometimes occurs when the device cannot connect with APNs over port
5223. If this happens, the user can move to another Wi-Fi network that isn’t blocking this port or,
on an iPhone or iPad, wait until the cellular data service becomes available. In either case, the
connection should then succeed and one of the delegation methods is called.
By requesting the device token and passing it to the provider every time your application launches, you help
to ensure that the provider has the current token for the device. If a user restores a backup to a device or
computer other than the one that the backup was created for (for example, the user migrates data to a new
device or computer), he or she must launch the application at least once for it to receive notifications again.
If the user restores backup data to a new device or computer, or reinstalls the operating system, the device
token changes. Moreover, never cache a device token and give that to your provider; always get the token
from the system whenever you need it. If your application has previously registered, calling
registerForRemoteNotificationTypes: resultsin the operating system passing the device token to the
delegate immediately without incurring additional overhead.
Listing 2-3 gives a simple example of how you might register for remote notifications in an iOS application.
The code would be nearly identical for a Mac app. (SendProviderDeviceToken is a hypothetical method
defined by the client in which it connects with its provider and passes it the device token.)
Scheduling, Registering, and Handling Notifications
Registering for Remote Notifications
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
20Listing 2-3 Registering for remote notifications
- (void)applicationDidFinishLaunching:(UIApplication *)app {
// other setup tasks here....
[[UIApplication sharedApplication]
registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge |
UIRemoteNotificationTypeSound)];
}
// Delegation methods
- (void)application:(UIApplication *)app
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
const void *devTokenBytes = [devToken bytes];
self.registered = YES;
[self sendProviderDeviceToken:devTokenBytes]; // custom method
}
- (void)application:(UIApplication *)app
didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
NSLog(@"Error in registration. Error: %@", err);
}
Handling Local and Remote Notifications
Let’s review the possible scenarios when the operating delivers a local notification or a remote notification for
an application.
● The notification is delivered when the application isn’t running in the foreground.
In this case, the system presents the notification, displaying an alert, badging an icon, perhaps playing a
sound.
● As a result of the presented notification, the user taps the action button of the alert or taps (or clicks) the
application icon.
If the action button is tapped (on a device running iOS), the system launches the application and the
application calls its delegate’s application:didFinishLaunchingWithOptions: method (if
implemented); it passesin the notification payload (for remote notifications) or the local-notification object
(for local notifications).
Scheduling, Registering, and Handling Notifications
Handling Local and Remote Notifications
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
21If the application icon is tapped on a device running iOS, the application calls the same method, but
furnishes no information about the notification . If the application icon is clicked on a computer running
OS X, the application calls the delegate’s applicationDidFinishLaunching: method in which the
delegate can obtain the remote-notification payload.
iOS Note: The application delegate could implement applicationDidFinishLaunching:
rather than application:didFinishLaunchingWithOptions:, but that is strongly
discouraged. The latter method allows the application to receive information related to the
reason for its launching, which can include things other than notifications.
● The notification is delivered when the application is running in the foreground.
The application calls its delegate’s application:didReceiveRemoteNotification: method (for
remote notifications) or application:didReceiveLocalNotification:method (forlocal notifications)
and passes in the notification payload or the local-notification object.
Note: The delegate methods cited in this section that have “RemoteNotification” in their name are
declared with identical signatures by by both NSApplicationDelegate Protocol and
UIApplicationDelegate.
An application can use the passed-in remote-notification payload or, in iOS, the UILocalNotification
object to help set the context for processing the item related to the notification. Ideally, the delegate does the
following on each platform to handle the delivery of remote and local notifications in all situations:
● For OS X, it should adopt the NSApplicationDelegate Protocol protocol and implement both the
applicationDidFinishLaunching: method and the
application:didReceiveRemoteNotification: method.
● For iOS, it should should adopt the UIApplicationDelegate protocol and implement both the
application:didFinishLaunchingWithOptions: method and the
application:didReceiveRemoteNotification: or
application:didReceiveLocalNotification: method.
Scheduling, Registering, and Handling Notifications
Handling Local and Remote Notifications
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
22iOS Note: In iOS, you can determine whether an application is launched as a result of the user
tapping the action button or whether the notification was delivered to the already-running application
by examining the application state. In the delegate’s implementation of the
application:didReceiveRemoteNotification: or
application:didReceiveLocalNotification: method, get the value of the
applicationState property and evaluate it. If the value is UIApplicationStateInactive, the
user tapped the action button; if the value is UIApplicationStateActive, the application was
frontmost when it received the notification.
The delegate for an iOS application in Listing 2-4 implements the
application:didFinishLaunchingWithOptions: method to handle a local notification. It gets the
associated UILocalNotification object from the launch-options dictionary using the
UIApplicationLaunchOptionsLocalNotificationKey key. From the UILocalNotification object’s
userInfo dictionary, it accesses the to-do item that is the reason for the notification and uses it to set the
application’s initial context. As shown in this example, you should appropriately reset the badge number on
the application icon—or remove it if there are no outstanding items—as part of handling the notification.
Listing 2-4 Handling a local notification when an application is launched
- (BOOL)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary
*)launchOptions {
UILocalNotification *localNotif =
[launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotif) {
NSString *itemName = [localNotif.userInfo objectForKey:ToDoItemKey];
[viewController displayItem:itemName]; // custom method
application.applicationIconBadgeNumber =
localNotif.applicationIconBadgeNumber-1;
}
[window addSubview:viewController.view];
[window makeKeyAndVisible];
return YES;
}
The implementation for a remote notification would be similar, except that you would use a specially declared
constant in each platform as a key to access the notification payload:
Scheduling, Registering, and Handling Notifications
Handling Local and Remote Notifications
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
23●
In iOS, the delegate, in its implementation of the application:didFinishLaunchingWithOptions:
method, usesthe UIApplicationLaunchOptionsRemoteNotificationKey key to accessthe payload
from the launch-options dictionary.
●
In OS X, the delegate, in its implementation of the applicationDidFinishLaunching: method, uses
the the NSApplicationLaunchRemoteNotificationKey key to access the payload dictionary from
the userInfo dictionary of the NSNotification object that is passed into the method.
The payload itself is an NSDictionary object that contains the elements of the notification—alert message,
badge number, sound, and so on. It can also contain custom data the application can use to provide context
when setting up the initial user interface. See “The Notification Payload” (page 35) for details about the
remote-notification payload.
Important: You should never define custom properties in the notification payload for the purpose of
transporting customer data or any other sensitive data. Delivery of remote notifications is not guaranteed.
One example of an appropriate usage for a custom payload property is a string identifying an email account
from which messages are downloaded to an email client; the application can incorporate this string in its
download user-interface. Another example of custom payload property is a timestamp for when the provider
first sent the notification; the client application can use this value to gauge how old the notification is.
When handling remote notifications in application:didFinishLaunchingWithOptions: or
applicationDidFinishLaunching:, the application delegate might perform a major additional task. Just
after the application launches, the delegate should connect with its provider and fetch the waiting data. Listing
2-5 gives a schematic illustration of this procedure.
Listing 2-5 Downloading data from a provider
- (void)application:(UIApplication *)app didFinishLaunchingWithOptions:(NSDictionary
*)opts {
// check launchOptions for notification payload and custom data, set UI context
[self startDownloadingDataFromProvider]; // custom method
app.applicationIconBadgeNumber = 0;
// other setup tasks here....
}
Scheduling, Registering, and Handling Notifications
Handling Local and Remote Notifications
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
24Note: A client application should always communicate with its provider asynchronously or on a
secondary thread.
The code in Listing 2-6 shows an implementation of the application:didReceiveLocalNotification:
method which, as you’ll recall, is called when application is running in the foreground. Here the application
delegate doesthe same work asit doesin Listing 2-4. It can accessthe UILocalNotification object directly
this time because this object is an argument of the method.
Listing 2-6 Handling a local notification when an application is already running
- (void)application:(UIApplication *)app
didReceiveLocalNotification:(UILocalNotification *)notif {
NSString *itemName = [notif.userInfo objectForKey:ToDoItemKey]
[viewController displayItem:itemName]; // custom method
application.applicationIconBadgeNumber =
notification.applicationIconBadgeNumber-1;
}
If you want your application to catch remote notifications that the system delivers while it is running in the
foreground, the application delegate should implement the
application:didReceiveRemoteNotification: method. The delegate should begin the procedure for
downloading the waiting data, message, or other item and, after this concludes, it should remove the badge
from the application icon. (If your application frequently checks with its provider for new data, implementing
this method might not be necessary.) The dictionary passed in the second parameter of this method is the
notification payload; you should not use any custom properties it contains to alter your application’s current
context.
Even though the only supported notification type for nonrunning applications in OS X is icon-badging, the
delegate can implement application:didReceiveRemoteNotification: to examine the notification
payload for other types of notifications and handle them appropriately (that is, display an alert or play a sound).
Scheduling, Registering, and Handling Notifications
Handling Local and Remote Notifications
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
25iOS Note: If the user unlocks the device shortly after a remote-notification alert is displayed, the
operating system automatically triggers the action associated with the alert. (This behavior is
consistent with SMS and calendar alerts.) This makes it even more important that actions related to
remote notifications do not have destructive consequences. A user should always make decisions
that result in the destruction of data in the context of the application that stores the data.
Passing the Provider the Current Language Preference (Remote
Notifications)
If an application doesn’t use the loc-key and loc-args properties of the aps dictionary for client-side
fetching of localized alert messages, the provider needs to localize the text of alert messages it puts in the
notification payload. To do this, however, the provider needs to know the language that the device user has
selected as the preferred language. (The user sets this preference in the General > International > Language
view of the Settings application.) The client application should send its provider an identifier of the preferred
language; this could be a canonicalized IETF BCP 47 language identifier such as “en” or “fr”.
Note: For more information about the loc-key and loc-args properties and client-side message
localizations, see “The Notification Payload” (page 35).
Listing 2-7 illustrates a technique for obtaining the currently selected language and communicating it to the
provider. In iOS, the array returned by the preferredLanguages of NSLocale contains one object: an
NSString object encapsulating the language code identifying the preferred language. The UTF8String
coverts the string object to a C string encoded as UTF8.
Listing 2-7 Getting the current supported language and sending it to the provider
NSString *preferredLang = [[NSLocale preferredLanguages] objectAtIndex:0];
const char *langStr = [preferredLang UTF8String];
[self sendProviderCurrentLanguage:langStr]; // custom method
}
The application might send its provider the preferred language every time the user changes something in the
current locale. To do this, you can listen for the notification named
NSCurrentLocaleDidChangeNotification and, in your notification-handling method, get the code
identifying the preferred language and send that to your provider.
Scheduling, Registering, and Handling Notifications
Passing the Provider the Current Language Preference (Remote Notifications)
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
26If the preferred language is not one the application supports, the provider should localize the message text in
a widely spoken fallback language such as English or Spanish.
Scheduling, Registering, and Handling Notifications
Passing the Provider the Current Language Preference (Remote Notifications)
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
27Apple Push Notification service (APNsforshort) isthe centerpiece of the push notificationsfeature. It is a robust
and highly efficientservice for propagating information to devicessuch asiPhone, iPad, and iPod touch devices.
Each device establishes an accredited and encrypted IP connection with the service and receives notifications
over this persistent connection. If a notification for an application arrives when that application is not running,
the device alerts the user that the application has data waiting for it.
Software developers (“providers”) originate the notifications in their server software. The provider connects
with APNs through a persistent and secure channel while monitoring incoming data intended for their client
applications. When new data for an application arrives, the provider prepares and sends a notification through
the channel to APNs, which pushes the notification to the target device.
In addition to being a simple but efficient and high-capacity transport service, APNs includes a default
quality-of-service component that provides store-and-forward capabilities. See “Quality of Service” (page 30)
for more information.
“Provider Communication with Apple Push Notification Service” (page 47) and “Scheduling, Registering, and
Handling Notifications” (page 15) discuss the specific implementation requirements for providers and iOS
applications, respectively.
A Push Notification and Its Path
Apple Push Notification service transports and routes a notification from a given provider to a given device.
A notification is a short message consisting of two major pieces of data: the device token and the payload. The
device token is analogous to a phone number; it contains information that enables APNs to locate the device
on which the client application is installed. APNs also uses it to authenticate the routing of a notification. The
payload is a JSON-defined property list thatspecifies how the user of an application on a device isto be alerted.
Note: For more information about the device token,see “Security Architecture” (page 30); for further
information about the notification payload, see “The Notification Payload” (page 35) .
The flow of remote-notification data is one-way. The provider composes a notification package that includes
the device token for a client application and the payload. The provider sends the notification to APNs which
in turn pushes the notification to the device.
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
28
Apple Push Notification ServiceWhen it authenticates itself to APNs, a provider furnishes the service with its topic, which identifies the
application for which it’s providing data. The topic is currently the bundle identifier of the target application
on an iOS device.
Figure 3-1 A push notification from a provider to a client application
Provider APNS notification notification
Client
App
iPhone
notification
Figure 3-1 is a greatly simplified depiction of the virtual network APNs makes possible among providers and
devices. The device-facing and provider-facing sides of APNs both have multiple points of connection; on the
provider-facing side, these are called gateways. There are typically multiple providers, each making one or
more persistent and secure connections with APNs through these gateways. And these providers are sending
notifications through APNs to many devices on which their client applications are installed. Figure 3-2 is a
slightly more realistic depiction.
Figure 3-2 Push notifications from multiple providers to multiple devices
APNS
Provider
B
Provider
A
Feedback Service
Sometimes APNs might attempt to deliver notifications for an application on a device, but the device may
repeatedly refuse delivery because there is no target application. This often happens when the user has
uninstalled the application. In these cases, APNs informs the provider through a feedback service that the
provider connects with. The feedback service maintains a list of devices per application for which there were
recent, repeated failed attempts to deliver notifications. The provider should obtain this list of devices and
stop sending notifications to them. For more on this service, see “The Feedback Service” (page 53).
Apple Push Notification Service
Feedback Service
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
29Quality of Service
Apple Push Notification Service includes a default Quality of Service (QoS) component that performs a
store-and-forward function. If APNs attempts to deliver a notification but the device is offline, the QoS stores
the notification. It retains only one notification per application on a device: the last notification received from
a provider for that application. When the offline device later reconnects, the QoS forwardsthe stored notification
to the device. The QoS retains a notification for a limited period before deleting it.
Security Architecture
To enable communication between a provider and a device, Apple Push Notification Service must expose
certain entry points to them. But then to ensure security, it must also regulate access to these entry points.
For this purpose, APNs requires two different levels of trust for providers, devices, and their communications.
These are known as connection trust and token trust.
Connection trust establishes certainty that, on one side, the APNs connection is with an authorized provider
with whom Apple has agreed to deliver notifications. At the device side of the connection, APNs must validate
that the connection is with a legitimate device.
After APNs has established trust at the entry points, it must then ensure that it conveys notificationsto legitimate
end points only. To do this, it must validate the routing of messages traveling through the transport; only the
device that is the intended target of a notification should receive it.
In APNs, assurance of accurate message routing—or token trust—is made possible through the device token.
A device token is an opaque identifier of a device that APNs gives to the device when it first connects with it.
The device shares the device token with its provider. Thereafter, this token accompanies each notification from
the provider. It is the basis for establishing trust that the routing of a particular notification is legitimate. (In a
metaphorical sense, it has the same function as a phone number, identifying the destination of a
communication.)
Note: A device token is not the same thing asthe device UDID returned by the uniqueIdentifier
property of UIDevice.
The following sections discuss the requisite components for connection trust and token trust as well as the
four procedures for establishing trust.
Apple Push Notification Service
Quality of Service
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
30Service-to-Device Connection Trust
APNs establishes the identity of a connecting device through TLS peer-to-peer authentication. (Note that iOS
takes care of this stage of connection trust; you do not need to implement anything yourself.) In the course
of this procedure, a device initiates a TLS connection with APNs, which returns its server certificate. The device
validates this certificate and then sends its device certificate to APNs, which validates that certificate.
TLS initiation
Device certificate
Server certificate
TLS established
Validate device certificate
Device APNS
Validate server certificate
Provider-to-Service Connection Trust
Connection trust between a provider and APNs is also established through TLS peer-to-peer authentication.
The procedure is similar to that described in “Service-to-Device Connection Trust” (page 31). The provider
initiates a TLS connection, getsthe server certificate from APNs, and validatesthat certificate. Then the provider
Apple Push Notification Service
Security Architecture
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
31sends its provider certificate to APNs, which validates it on its end. Once this procedure is complete, a secure
TLS connection has been established; APNsis now satisfied that the connection has been made by a legitimate
provider.
TLS initiation
Provider certificate
Server certificate
TLS established
Validate provider certificate
Provider APNS
Validate server certificate
Note that provider connection is valid for delivery to only one specific application, identified by the topic
(bundle ID) specified in the certificate. APNs also maintains a certificate revocation list; if a provider’s certificate
is on this list, APNs may revoke provider trust (that is, refuse the connection).
Token Generation and Dispersal
An iOS-based application must register to receive push notifications; it typically does this right after it is
installed on a device. (This procedure is described in “Scheduling, Registering, and Handling Notifications” (page
15).) iOS receives the registration request from an application, connects with APNs, and forwards the request.
Apple Push Notification Service
Security Architecture
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
32APNs generates a device token using information contained in the unique device certificate. The device token
contains an identifier of the device. It then encrypts the device token with a token key and returns it to the
device.
Token
Connect (Token, ...)
Token
Generate token package
Encrypt token with token key
Generate device ID from
device certificate
Provider Device APNS
The device returns the device token to the requesting application as an NSData object. The application then
must then deliver the device token to its provider in either binary or hexadecimal format. Figure 3-3 also
illustratesthe token generation and dispersalsequence, but in addition showsthe role of the client application
in furnishing its provider with the device token.
Figure 3-3 Sharing the device token
deviceToken
APNS
Provider
Client
App
2
1
3
4
SSL connect
deviceToken
deviceToken
The form of this phase of token trust ensures that only APNs generates the token which it will later honor, and
it can assure itself that a token handed to it by a device is the same token that it previously provisioned for
that particular device—and only for that device.
Apple Push Notification Service
Security Architecture
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
33Token Trust (Notification)
After iOS obtains a device token from APNs, as described in “Token Generation and Dispersal” (page 32), it
must provide APNs with the token every time it connects with it. APNs decrypts the device token and validates
that the token was generated for the connecting device. To validate, APNs ensures that the device identifier
contained in the token matches the device identifier in the device certificate.
Every notification that a provider sends to APNs for delivery to a device must be accompanied by the device
token it obtained from an application on that device. APNs decrypts the token using the token key, thereby
ensuring that the notification is valid. It then uses the device ID contained in the device token to determine
the destination device for the notification.
Token, Payload
Response (OK)
Payload
Connect (Token, ...)
Decrypt token and validate
with device certificate
Provider APNS Device
Decrypt token with
token key
Trust Components
To support the security model for APNs, providers and devices must possess certain certificates, certificate
authority (CA) certificates, or tokens.
● Provider: Each provider requires a unique provider certificate and private cryptographic key for validating
their connection with APNs. This certificate, provisioned by Apple, must identify the particular topic
published by the provider; the topic is the bundle ID of the client application. For each notification, the
provider must furnish APNs with a device token identifying the target device. The provider may optionally
wish to validate the service it is connecting to using the public server certificate provided by the APNs
server.
Apple Push Notification Service
Security Architecture
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
34● Device: iOS uses the public server certificate passed to it by APNs to authenticate the service that it has
connected to. It has a unique private key and certificate that it uses to authenticate itself to the service
and establish the TLS connection. It obtains the device certificate and key during device activation and
stores them in the keychain. iOS also holds its particular device token, which it receives during the service
connection process. Each registered client application isresponsible for delivering thistoken to its content
provider.
APNs servers also have the necessary certificates, CA certificates, and cryptographic keys (private and public)
for validating connections and the identities of providers and devices.
The Notification Payload
Each push notification carries with it a payload. The payload specifies how users are to be alerted to the data
waiting to be downloaded to the client application. The maximum size allowed for a notification payload is
256 bytes; Apple Push Notification Service refuses any notification that exceeds this limit. Remember that
delivery of notifications is “best effort” and is not guaranteed.
For each notification, providers must compose a JSON dictionary object that strictly adheres to RFC 4627. This
dictionary must contain another dictionary identified by the key aps. The aps dictionary contains one or more
properties that specify the following actions:
● An alert message to display to the user
● A number to badge the application icon with
● A sound to play
Note: Although you can combine an alert message, icon badging, and a sound in a single notification,
you should consider the human-interface implications with push notifications. For example, a user
might find frequent alert messages with accompanying sound more annoying than useful, especially
when the data to be downloaded is not critical.
If the target application isn’t running when the notification arrives, the alert message, sound, or badge value
is played orshown. If the application isrunning, iOS deliversit to the application delegate as an NSDictionary
object. The dictionary contains the corresponding Cocoa property-list objects (plus NSNull).
Providers can specify custom payload values outside the Apple-reserved aps namespace. Custom values must
use the JSON structured and primitive types: dictionary (object), array,string, number, and Boolean. You should
not include customer information as custom payload data. Instead, use it for such purposes as setting context
(for the user interface) or internal metrics. For example, a custom payload value might be a conversation
Apple Push Notification Service
The Notification Payload
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
35identifier for use by an instant-message client application or a timestamp identifying when the provider sent
the notification. Any action associated with an alert message should not be destructive—for example, deleting
data on the device.
Important: Because delivery is not guaranteed, you should not depend on the remote-notificationsfacility
for delivering critical data to an application via the payload. And never include sensitive data in the payload.
You should use it only to notify the user that new data is available.
Table 3-1 lists the keys and expected values of the aps payload.
Table 3-1 Keys and values of the aps dictionary
Key Value type Comment
If this property is included, iOS displays a standard alert. You may specify a
string asthe value of alert or a dictionary asits value. If you specify a string,
it becomes the message text of an alert with two buttons: Close and View.
If the user taps View, the application is launched.
Alternatively, you can specify a dictionary as the value of alert. See Table
3-2 (page 36) for descriptions of the keys of this dictionary.
string or
dictionary
alert
The number to display as the badge of the application icon. If this property
is absent, the badge is not changed. To remove the badge, set the value of
this property to 0.
badge number
The name of a sound file in the application bundle. The sound in this file is
played as an alert. If the sound file doesn’t exist or default is specified as
the value, the default alert sound is played. The audio must be in one of the
audio data formats that are compatible with system sounds; see “Preparing
Custom Alert Sounds” (page 15) for details.
sound string
Table 3-2 Child properties of the alert property
Value Comment
type
Key
body string The text of the alert message.
Apple Push Notification Service
The Notification Payload
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
36Value Comment
type
Key
If a string is specified, displays an alert with two buttons, whose
behavior is described in Table 3-1. However, iOS uses the string
as a key to get a localized string in the current localization to use
for the right button’s title instead of “View”. If the value is null,
the system displays an alert with a single OK button that simply
dismisses the alert when tapped. See “Localized Formatted
Strings” (page 37) for more information.
string or
null
action-loc-key
A key to an alert-message string in a Localizable.strings file
for the current localization (which is set by the user’s language
preference). The key string can be formatted with %@ and %n$@
specifiers to take the variables specified in loc-args. See
“Localized Formatted Strings” (page 37) for more information.
loc-key string
Variable string values to appear in place of the format specifiers
in loc-key. See “Localized Formatted Strings” (page 37) for more
information.
array of
strings
loc-args
The filename of an image file in the application bundle; it may
include the extension or omit it. The image is used as the launch
image when userstap the action button or move the action slider.
If this property is notspecified, the system either usesthe previous
snapshot,uses the image identified by the UILaunchImageFile
key in the application’s Info.plist file, or falls back to
Default.png.
This property was added in iOS 4.0.
launch-image string
Note: If you want the iPhone, iPad, or iPod touch device to display the message text as-is in an alert
that has both the Close and View buttons, then specify a string as the direct value of alert. Don’t
specify a dictionary as the value of alert if the dictionary only has the body property.
Localized Formatted Strings
You can display localized alert messages in two ways. The server originating the notification can localize the
text; to do this, it must discover the current language preference selected for the device (see “Passing the
Provider the Current Language Preference (Remote Notifications)” (page 26)). Or the client application can
store in its bundle the alert-message strings translated for each localization it supports. The provider specifies
the loc-key and loc-args properties in the aps dictionary of the notification payload. When the device
receives the notification (assuming the application isn’t running), it uses these aps-dictionary properties to
find and format the string localized for the current language, which it then displays to the user.
Apple Push Notification Service
The Notification Payload
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
37Here’s how that second option works in a little more detail.
An iOS application can internationalize resources such as images, sounds, and text for each language that it
supports, Internationalization collects the resources and puts them in a subdirectory of the bundle with a
two-part name: a language code and an extension of .lproj (for example, fr.lproj). Localized strings that
are programmatically displayed are put in a file called Localizable.strings. Each entry in this file has a
key and a localized string value; the string can have format specifiers for the substitution of variable values.
When an application asksfor a particular resource—say a localized string—it getsthe resource that islocalized
for the language currently selected by the user. For example, if the preferred language is French, the
corresponding string value for an alert message would be fetched from Localizable.strings in the
fr.lproj directory in the application bundle. (iOS makes this request through the NSLocalizedString
macro.)
Note: This general pattern is also followed when the value of the action-loc-key property is a
string. This string is a key into the Localizable.strings in the localization directory for the
currently selected language. iOS uses this key to get the title of the button on the right side of an
alert message (the “action” button).
To make this clearer, let’s consider an example. The provider specifies the following dictionary as the value of
the alert property:
"alert" : { "loc-key" : "GAME_PLAY_REQUEST_FORMAT", "loc-args" : [ "Jenna", "Frank"]
},
When the device receives the notification, it uses "GAME_PLAY_REQUEST_FORMAT" as a key to look up the
associated string value in the Localizable.strings file in the .lproj directory for the current language.
Assuming the current localization has an Localizable.strings entry such as this:
"GAME_PLAY_REQUEST_FORMAT" = "%@ and %@ have invited you to play Monopoly";
the device displays an alert with the message “Jenna and Frank have invited you to play Monopoly”.
In addition to the format specifier %@, you can %n$@ format specifiers for positional substitution of string
variables. The n is the index (starting with 1) of the array value in loc-args to substitute. (There’s also the %%
specifier for expressing a percentage sign (%).) So if the entry in Localizable.strings is this:
"GAME_PLAY_REQUEST_FORMAT" = "%2$@ and %1$@ have invited you to play Monopoly";
Apple Push Notification Service
The Notification Payload
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
38the device displays an alert with the message "Frank and Jenna have invited you to play Monopoly".
For a full example of a notification payload that usesthe loc-key and loc-arg properties,see the last example
of “Examples of JSON Payloads.” To learn more about internationalization in iOS, see ““Advanced App Tricks”” in
iOS App Programming Guide ; for general information about internationalization, see Internationalization
Programming Topics. String formatting is discussed in “Formatting String Objects” in String Programming Guide .
Note: You should use the loc-key and loc-args properties—and the alert dictionary in
general—only if you absolutely need to. The values of these properties, especially if they are long
strings, might use up more bandwidth than is good for performance. Many if not most applications
may not need these properties because their message strings are originated by users and thus are
implicitly "localized."
Examples of JSON Payloads
The following examples of the payload portion of notifications illustrate the practical use of the properties
listed in Table 3-1. Properties with “acme” in the key name are examples of custom payload data. The examples
include whitespace and newline characters for readability; for better performance, providers should omit
whitespace and newline characters.
Example 1: The following payload has an aps dictionary with a simple, recommended form for alert messages
with the default alert buttons (Close and View). It uses a string as the value of alert rather than a dictionary.
This payload also has a custom array property.
{
"aps" : { "alert" : "Message received from Bob" },
"acme2" : [ "bang", "whiz" ]
}
Example 2. The payload in the example uses an aps dictionary to request that the device display an alert
message with an Close button on the left and a localized title for the "action" button on the right side of the
alert. In this case, “PLAY” is used as a key into the Localizable.strings file for the currently selected
language to get the localized equivalent of “Play”. The aps dictionary also requests that the application icon
be badged with 5.
{ "aps" : { "alert" : { "body" : "Bob wants to play poker",
"action-loc-key" : "PLAY" }, "badge" : 5, }, "acme1" : "bar",
"acme2" : [ "bang", "whiz" ] }
Apple Push Notification Service
The Notification Payload
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
39Example 3. The payload in this example specifies that device should display an alert message with both Close
and View buttons. It also request that the application icon be badged with 9 and that a bundled alert sound
be played when the notification is delivered.
{
"aps" : {
"alert" : "You got your emails.",
"badge" : 9,
"sound" : "bingbong.aiff"
},
"acme1" : "bar",
"acme2" : 42
}
Example 4. The interesting thing about the payload in this example is that it uses the loc-key and loc-args
child properties of the alert dictionary to fetch a formatted localized string from the application’s bundle
and substitute the variable string values(loc-args) in the appropriate places. It also specifies a custom sound
and include a custom property.
{
"aps" : {
"alert" : { "loc-key" : "GAME_PLAY_REQUEST_FORMAT", "loc-args" : [ "Jenna",
"Frank"] },
"sound" : "chime"
},
"acme" : "foo"
}
Example 5. The following example shows an empty aps dictionary; because the badge property is missing,
any current badge number shown on the application icon is removed. The acme2 custom property is an array
of two integers.
{
"aps" : {
},
"acme2" : [ 5, 8 ]
Apple Push Notification Service
The Notification Payload
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
40}
Remember, for better performance, you should strip all whitespace and newline characters from the payload
before including it in the notification.
Apple Push Notification Service
The Notification Payload
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
41Sandbox and Production Environments
To develop and deploy the provider side of a client/server application, you must get SSL certificates from the
appropriate Dev Center. Each certificate is limited to a single application, identified by its bundle ID. Each
certificate is also limited to one of two development environments, each with its own assigned IP address:
● Sandbox: The sandbox environment is used for initial development and testing of the provider application.
It provides the same set of services as the production environment, although with a smaller number of
server units. The sandbox environment also acts a virtual device, enabling simulated end-to-end testing.
You accessthe sandbox environment at gateway.sandbox.push.apple.com, outbound TCP port 2195.
● Production: Use the production environment when building the production version of the provider
application. Applications using the production environment must meet Apple’s reliability requirements.
You access the production environment at gateway.push.apple.com, outbound TCP port 2195.
You must getseparate certificatesfor the sandbox (development) environment and the production environment.
The certificates are associated with an identifier of the application that is the recipient of push notifications;
this identifier includes the application’s bundle ID. When you create a provisioning profile for one of the
environments, the requisite entitlements are automatically added to the profile, including the entitlement
specific to push notifications, . The two provisioning profiles are called Development
and Distribution. The Distribution provisioning profile is a requirement for submitting your application to the
App Store.
OS X Note: The entitlement for the OS X provisioning profile is
com.apple.developer.aps-environment, which scopes it to the platform.
You can determine in Xcode which environment you are in by the selection of a code-signing identity. If you
see an “iPhone Developer: Firstname Lastname ” certificate/provisioning profile pair, you are in the sandbox
environment. If you see an “iPhone Distribution: Companyname ” certificate/provisioning profile pair, you are
in the production environment. It is a good idea to create a Distribution release configuration in Xcode to help
you further differentiate the environments.
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
42
Provisioning and DevelopmentAlthough an SSL certificate is not put into a provisioning profile, the is added to the
profile because of the association of the certificate and a particular application ID. As a result this entitlement
is built into the application, which enables it to receive push notifications.
Provisioning Procedures
In the iOS Developer Program, each member on a development team has one of three roles: team agent, team
admin, and team member. The roles differ in relation to iPhone development certificates and provisioning
profiles. The team agent isthe only person on the team who can create Development (Sandbox) SSL certificates
and Distribution (Production) SSL certificates. The team admin and the team agent can both create both
Development and Distribution provisioning profiles. Team members may only download and install certificates
and provisioning profiles. The procedures in the following sections make reference to these roles.
Note: The iOS Provisioning Portal makes available to all iOS Developer Program members a user
guide and a series of videos that explain all aspects of certificate creation and provisioning. The
following sections focus on APNs-specific aspects of the process and summarize other aspects. To
access the portal, iOS Developer Program members should go to the iOS Dev Center (http://developer.apple.com/devcenter/ios), log in, and click then go to the iOS Provisioning Portal page (there’s
a link in the upper right).
Creating the SSL Certificate and Keys
In the provisioning portal of the iOS Dev Center, the team agent selects the application IDs for APNs. He also
completes the following steps to create the SSL certificate:
1. Click App IDs in the sidebar on the left side of the window.
The next page displays your valid application IDs. An application ID consists of an application’s bundle ID
prefixed with a ten-character code generated by Apple. The team admin must enter the bundle ID. For a
certificate, it must incorporate a specific bundle ID; you cannot use a “wildcard” application ID.
2. Locate the application ID for the sandbox SSL certificate (and that is associated with the Development
provisioning profile) and click Configure.
You must see “Available” under the Apple Push Notification Service column to configure a certificate for
this application ID.
3. In the Configure App ID page, check the Enable Push Notification Services box and click the Configure
button.
Clicking this button launches an APNs Assistant, which guides you through the next series of steps.
Provisioning and Development
Provisioning Procedures
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
434. The first step requires that you launch the Keychain Access application and generate a Certificate Signing
Request (CSR).
Follow the instructions presented in the assistant. When you are finished generating a CSR, click Continue
in Keychain Access to return to the APNs Assistant.
When you create a CSR, Keychain Access generates a private and a public cryptographic key pair. The
private key is put into your Login keychain by default. The public key is included in the CSR sent to the
provisioning authority. When the provisioning authority sendsthe certificate back to you, one of the items
in that certificate is the public key.
5. In the Submit Certificate Signing Request pane, click Choose File. Navigate to the CSR file you created in
the previous step and select it.
6. Click the Generate button.
While displaying the Generate Your Certificate pane, the Assistant configures and generates your Client
SSL Certificate. If it succeeds, it displays the message “Your APNs Certificate has been generated.” Click
Continue to proceed to the next step.
7. In the next pane, click the Download Now button to download the certificate file to your download location.
Navigate to that location and double-click the certificate file (which has an extension of cer) to install it
in your keychain. When you are finished, click Done in the APNs Assistant.
Double-clicking the file launches Keychain Access. Make sure you install the certificate in your login keychain
on the computer you are using for provider development. In Keychain Access, ensure that your certificate
user ID matches your application’s bundle ID. The APNs SSL certificate should be installed on your
notification server.
When you finish these steps you are returned to the Configure App ID page of the iOS Dev Center portal. The
certificate should be badged with a green circle and the label “Enabled”.
To create a certificate for the production environment, repeat the same procedure but choose the application
ID for the production certificate.
Creating and Installing the Provisioning Profile
The Team Admin or Team Agent must next create the provisioning profile (Development or Distribution) used
in the server side of remote-notification development. The provisioning profile is a collection of assets that
associates developers of an application and their devices with an authorized development team and enables
those devicesto be used for testing. The profile contains certificates, device identifiers, the application’s bundle
ID, and all entitlements, including . All team members must install the provisioning
profile on the devices on which they will run and test application code.
Provisioning and Development
Provisioning Procedures
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
44Note: Refer to the program user guide for the details of creating a provisioning profile.
To download and install the provisioning profile, team members should complete the following steps:
1. Go to the provisioning portal in the iOS Dev Center.
2. Create a new provisioning profile that contains the App ID you registered for APNs.
3. Modify any existing profile before you download the new one.
You have to modify the profile in some minor way (for example, toggle an option) for the portal to generate
a new provisioning profile. If the profile isn't so “dirtied,” you're given the original profile without the push
entitlements.
4. From the download location, drag the profile file (which has an extension of mobileprovision) onto
the Xcode or iTunes application icons.
Alternatively, you can move the profile file to ~/Library/MobileDevice/Provisioning Profiles.
Create the directory if it does not exist.
5. Verify that the entitlements in the provisioning-profile file are correct. To do this, open the
.mobileprovision file in a text editor. The contents of the file are structured in XML. In the Entitlements
dictionary locate the aps-environment key. For a development provisioning profile, the string value of
this key should be development; for a distribution provisioning profile, the string value should be
production.
6. In the Xcode Organizer window, go the Provisioning Profiles section and install the profile on your device.
When you build the project, the binary is now signed by the certificate using the private key.
Installing the SSL Certificate and Key on the Server
You should install the SSL distribution certificate and private cryptographic key you obtained earlier on the
server computer on which the provider code runs and from which it connects with the sandbox or production
versions of APNs. To do so, complete the following steps:
1. Open Keychain Access utility and click the My Certificates category in the left pane.
2. Find the certificate you want to install and disclose its contents.
You'll see both a certificate and a private key.
3. Select both the certificate and key, choose File > Export Items, and export them as a Personal Information
Exchange (.p12) file.
4. Servers implemented in languages such as Ruby and Perl often are better able to deal with certificates in
the Personal Information Exchange format. To convert the certificate to thisformat, complete the following
steps:
Provisioning and Development
Provisioning Procedures
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
45a. In KeyChain Access,select the certificate and choose File > Export Items. Select the Personal Information
Exchange (.p12) option, select a save location, and click Save.
b. Launch the Terminal application and enter the following command after the prompt:
openssl pkcs12 -in CertificateName.p12 -out CertificateName.pem -nodes
5. Copy the .pem certificate to the new computer and install it in the appropriate place.
Provisioning and Development
Provisioning Procedures
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
46This chapter describesthe interfacesthat providers use for communication with Apple Push Notification service
(APNs) and discusses some of the functions that providers are expected to fulfill.
General Provider Requirements
As a provider you communicate with Apple Push Notification service over a binary interface. This interface is
a high-speed, high-capacity interface for providers; it uses a streaming TCP socket design in conjunction with
binary content. The binary interface is asynchronous.
The binary interface of the production environment is available through gateway.push.apple.com, port
2195; the binary interface of the sandbox (development) environment is available through
gateway.sandbox.push.apple.com, port 2195. You may establish multiple, parallel connections to the
same gateway or to multiple gateway instances.
For each interface you should use TLS (or SSL) to establish a secured communications channel. The SSL certificate
required for these connections is provisioned through the iOS Provisioning Portal. (See “Provisioning and
Development” (page 42) for details.) To establish a trusted provider identity, you should present this certificate
to APNs at connection time using peer-to-peer authentication.
Note: To establish a TLS session with APNs, an Entrust Secure CA root certificate must be installed
on the provider’s server. If the server is running OS X, this root certificate is already in the keychain.
On other systems, the certificate might not be available. You can download this certificate from the
Entrust SSL Certificates website.
You should also retain connections with APNs across multiple notifications. APNs may consider connections
that are rapidly and repeatedly established and torn down as a denial-of-service attack. Upon error, APNs closes
the connection on which the error occurred.
As a provider, you are responsible for the following aspects of push notifications:
● You must compose the notification payload (see “The Notification Payload” (page 35)).
● You are responsible for supplying the badge number to be displayed on the application icon.
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
47
Provider Communication with Apple Push
Notification Service● You should regularly connect with the feedback web server and fetch the current list of those devices that
have repeatedly reported failed-delivery attempts. Then you should cease sending notifications to the
devices associated with those applications. See “The Feedback Service” (page 53) for more information.
If you intend to support notification messagesin multiple languages, but do not use the loc-key and loc-args
properties of the aps payload dictionary for client-side fetching of localized alert strings, you need to localize
the text of alert messages on the server side. To do this, you need to find out the current language preference
from the client application. “Scheduling, Registering, and Handling Notifications” (page 15) suggests an
approach for obtaining this information. See “The Notification Payload” (page 35) for information about the
loc-key and loc-args properties.
The Binary Interface and Notification Formats
The binary interface employs a plain TCP socket for binary content that is streaming in nature. For optimum
performance, you should batch multiple notificationsin a single transmission over the interface, either explicitly
or using a TCP/IP Nagle algorithm.
The interface supports two formats for notification packets, a simple format and an enhanced format that
addresses some of the issues with the simple format:
● Notification expiry. APNs has a store-and-forward feature that keeps the most recent notification sent to
an application on a device. If the device is offline at time of delivery, APNs delivers the notification when
the device next comes online. With the simple format, the notification is delivered regardless of the
pertinence of the notification. In other words, the notification can become “stale” over time. The enhanced
format includes an expiry value that indicates the period of validity for a notification. APNs discards a
notification in store-and-forward when this period expires.
● Error response. With the simple format, if you send a notification packet that is malformed in some
way—for example, the payload exceeds the stipulated limit—APNs responds by severing the connection.
It gives no indication why it rejected the notification. The enhanced format lets a provider tag a notification
with an arbitrary identifier. If there is an error, APNs returns a packet that associates an error code with
the identifier. This response enables the provider to locate and correct the malformed notification.
The enhanced format is recommended for most providers.
Provider Communication with Apple Push Notification Service
The Binary Interface and Notification Formats
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
48Let’s examine the simple notification format first because much of this format is shared with the enhanced
format. Figure 5-1 illustrates this format.
Figure 5-1 Simple notification format
0 0 32 deviceToken (binary) 0 34 {"aps":{"alert":"You have mail!"}}
Bytes: 1 2 32 2
Command
Token length Payload length
(big endian) (big endian)
34
The first byte in the simple format is a command value of 0 (zero). The lengths of the device token and the
payload must be in network order (that is, big endian). In addition, you should encode the device token in
binary format. The payload must not exceed 256 bytes and must not be null-terminated.
Listing 5-1 gives an example of a function that sends a push notification to APNs over the binary interface
using the simple notification format. The example assumes prior SSL connection to gateway.push.apple.com
(or gateway.sandbox.push.apple.com) and peer-exchange authentication.
Listing 5-1 Sending a notification in the simple format via the binary interface
static bool sendPayload(SSL *sslPtr, char *deviceTokenBinary, char *payloadBuff,
size_t payloadLength)
{
bool rtn = false;
if (sslPtr && deviceTokenBinary && payloadBuff && payloadLength)
{
uint8_t command = 0; /* command number */
char binaryMessageBuff[sizeof(uint8_t) + sizeof(uint16_t) +
DEVICE_BINARY_SIZE + sizeof(uint16_t) + MAXPAYLOAD_SIZE];
/* message format is, |COMMAND|TOKENLEN|TOKEN|PAYLOADLEN|PAYLOAD| */
char *binaryMessagePt = binaryMessageBuff;
uint16_t networkOrderTokenLength = htons(DEVICE_BINARY_SIZE);
uint16_t networkOrderPayloadLength = htons(payloadLength);
/* command */
*binaryMessagePt++ = command;
/* token length network order */
Provider Communication with Apple Push Notification Service
The Binary Interface and Notification Formats
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
49memcpy(binaryMessagePt, &networkOrderTokenLength, sizeof(uint16_t));
binaryMessagePt += sizeof(uint16_t);
/* device token */
memcpy(binaryMessagePt, deviceTokenBinary, DEVICE_BINARY_SIZE);
binaryMessagePt += DEVICE_BINARY_SIZE;
/* payload length network order */
memcpy(binaryMessagePt, &networkOrderPayloadLength, sizeof(uint16_t));
binaryMessagePt += sizeof(uint16_t);
/* payload */
memcpy(binaryMessagePt, payloadBuff, payloadLength);
binaryMessagePt += payloadLength;
if (SSL_write(sslPtr, binaryMessageBuff, (binaryMessagePt -
binaryMessageBuff)) > 0)
rtn = true;
}
return rtn;
}
Figure 5-2 depicts the enhanced format for notification packets. With this format, if APNs encounters an
unintelligible command, it returns an error response before disconnecting.
Figure 5-2 Enhanced notification format
1 Identifier
Bytes: 1 4
Expiry
4
Command
32 deviceToken (binary) 0 34 {"aps":{"alert":"You have mail!"}}
2 32 2
Token length Payload length
(big endian) (big endian)
34
0
The first byte in the enhanced notification format is a command value of 1. The two new fields in this format
are for an identifier and an expiry value. (Everything else is the same as the simple notification format.)
●
Identifier—An arbitrary value that identifies this notification. This same identifier is returned in a
error-response packet if APNs cannot interpret a notification.
Provider Communication with Apple Push Notification Service
The Binary Interface and Notification Formats
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
50● Expiry—A fixed UNIX epoch date expressed in seconds (UTC) that identifies when the notification is no
longer valid and can be discarded. The expiry value should be in network order (big endian). If the expiry
value is positive, APNs tries to deliver the notification at least once. You can specify zero or a value less
than zero to request that APNs not store the notification at all.
If you send a notification and APNs finds the notification malformed or otherwise unintelligible, it returns an
error-response packet prior to disconnecting. (If there is no error, APNs doesn’t return anything.) Figure 5-3
depicts the format of the error-response packet.
Figure 5-3 Format of error-response packet
8
Bytes: 1
Command Status
n Identifier
1 4
The packet has a command value of 8 followed by a one-byte status code and the same notification identifier
specified by the provider when it composed the notification. Table 5-1 lists the possible status codes and their
meanings.
Table 5-1 Codes in error-response packet
Status code Description
0 No errors encountered
1 Processing error
2 Missing device token
3 Missing topic
4 Missing payload
5 Invalid token size
6 Invalid topic size
7 Invalid payload size
8 Invalid token
255 None (unknown)
Provider Communication with Apple Push Notification Service
The Binary Interface and Notification Formats
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
51Listing 5-2 modifies the code in Listing 5-1 (page 49) to compose a push notification in the enhanced format
before sending it to APNs. As with the earlier example, it assumes prior SSL connection to
gateway.push.apple.com (or gateway.sandbox.push.apple.com) and peer-exchange authentication.
Listing 5-2 Sending a notification in the enhanced format via the binary interface
static bool sendPayload(SSL *sslPtr, char *deviceTokenBinary, char *payloadBuff,
size_t payloadLength)
{
bool rtn = false;
if (sslPtr && deviceTokenBinary && payloadBuff && payloadLength)
{
uint8_t command = 1; /* command number */
char binaryMessageBuff[sizeof(uint8_t) + sizeof(uint32_t) + sizeof(uint32_t)
+ sizeof(uint16_t) +
DEVICE_BINARY_SIZE + sizeof(uint16_t) + MAXPAYLOAD_SIZE];
/* message format is, |COMMAND|ID|EXPIRY|TOKENLEN|TOKEN|PAYLOADLEN|PAYLOAD|
*/
char *binaryMessagePt = binaryMessageBuff;
uint32_t whicheverOrderIWantToGetBackInAErrorResponse_ID = 1234;
uint32_t networkOrderExpiryEpochUTC = htonl(time(NULL)+86400); // expire
message if not delivered in 1 day
uint16_t networkOrderTokenLength = htons(DEVICE_BINARY_SIZE);
uint16_t networkOrderPayloadLength = htons(payloadLength);
/* command */
*binaryMessagePt++ = command;
/* provider preference ordered ID */
memcpy(binaryMessagePt, &whicheverOrderIWantToGetBackInAErrorResponse_ID,
sizeof(uint32_t));
binaryMessagePt += sizeof(uint32_t);
/* expiry date network order */
memcpy(binaryMessagePt, &networkOrderExpiryEpochUTC, sizeof(uint32_t));
binaryMessagePt += sizeof(uint32_t);
Provider Communication with Apple Push Notification Service
The Binary Interface and Notification Formats
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
52/* token length network order */
memcpy(binaryMessagePt, &networkOrderTokenLength, sizeof(uint16_t));
binaryMessagePt += sizeof(uint16_t);
/* device token */
memcpy(binaryMessagePt, deviceTokenBinary, DEVICE_BINARY_SIZE);
binaryMessagePt += DEVICE_BINARY_SIZE;
/* payload length network order */
memcpy(binaryMessagePt, &networkOrderPayloadLength, sizeof(uint16_t));
binaryMessagePt += sizeof(uint16_t);
/* payload */
memcpy(binaryMessagePt, payloadBuff, payloadLength);
binaryMessagePt += payloadLength;
if (SSL_write(sslPtr, binaryMessageBuff, (binaryMessagePt - binaryMessageBuff))
> 0)
rtn = true;
}
return rtn;
}
Take note that the device token in the production environment and the device token in the development
(sandbox) environment are not the same value.
The Feedback Service
If a provider attempts to deliver a push notification to an application, but the application no longer exists on
the device, the device reports that fact to Apple Push Notification Service. This situation often happens when
the user has uninstalled the application. If a device reports failed-delivery attempts for an application, APNs
needs some way to inform the provider so that it can refrain from sending notifications to that device. Doing
this reduces unnecessary message overhead and improves overall system performance.
For this purpose Apple Push Notification Service includes a feedback service that APNs continually updates
with a per-application list of devices for which there were failed-delivery attempts. The devices are identified
by device tokens encoded in binary format. Providers should periodically query the feedback service to get
Provider Communication with Apple Push Notification Service
The Feedback Service
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
53the list of device tokens for their applications, each of which is identified by its topic. Then, after verifying that
the application hasn’t recently been re-registered on the identified devices, a provider should stop sending
notifications to these devices.
Access to the feedback service takes place through a binary interface similar to that used for sending push
notifications. You access the production feedback service via feedback.push.apple.com, port 2196; you
access the sandbox feedback service via feedback.sandbox.push.apple.com, port 2196. As with the
binary interface for push notifications, you must use TLS (or SSL) to establish a secured communications channel.
The SSL certificate required for these connections is the same one that is provisioned for sending notifications.
To establish a trusted provider identity, you should present this certificate to APNs at connection time using
peer-to-peer authentication.
Once you are connected, transmission begins immediately; you do not need to send any command to APNs.
Begin reading the stream written by the feedback service until there is no more data to read. The received
data is in tuples having the following format:
Figure 5-4 Binary format of a feedback tuple
n n n n 0 32 deviceToken (binary)
Bytes: 4 2 32
Token length
time_t
(big endian) (big endian)
A timestamp (as a four-byte time_t value) indicating when the APNs determined
that the application no longer exists on the device. This value, which is in network
order, represents the seconds since 1970, anchored to UTC.
You should use the timestamp to determine if the application on the device
re-registered with your service since the moment the device token was recorded on
the feedback service. If it hasn’t, you should cease sending push notifications to the
device.
Timestamp
Token length The length of the device token as a two-byte integer value in network order.
Device token The device token in binary format.
Note: APNs monitors providers for their diligence in checking the feedback service and refraining
from sending push notifications to nonexistent applications on devices.
Provider Communication with Apple Push Notification Service
The Feedback Service
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
54This table describes the changes to Local and Push Notification Programming Guide .
Date Notes
Added information about implementing push notifications on an OS X
desktop client. Unified the guide for iOS and OS X.
2011-08-09
Describes how to determine if an application is launched because the
user tapped the notification alert's action button.
2010-08-03
2010-07-08 Changed occurrences of "iPhone OS" to "iOS."
Updated and reorganized to describe local notifications, a feature
introduced in iOS 4.0. Also describes a new format for push notifications
sent to APNs.
2010-05-27
2010-01-28 Made many small corrections.
Made minor corrections and linked to short inline articles on Cocoa
concepts.
2009-08-14
Added notes about Wi-Fi and frequency of registration, and gateway
address for sandbox. Updated with various clarifications and
enhancements.
2009-05-22
First version of a document that explains how providers can send push
notifications to client applications using Apple Push Notification Service.
2009-03-15
2011-08-09 | © 2011 Apple Inc. All Rights Reserved.
55
Document Revision HistoryApple Inc.
© 2011 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Cocoa, iPad, iPhone, iPod,
iPod touch, iTunes, Keychain, Mac, OS X,
QuickTime, Sand, and Xcode are trademarks of
Apple Inc., registered in the U.S. and other
countries.
App Store is a service mark of Apple Inc.
Times is a registered trademark of Heidelberger
Druckmaschinen AG, available from Linotype
Library GmbH.
UNIX is a registered trademark of The Open
Group.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
Mac App Programming
GuideContents
About OS X App Design 7
At a Glance 7
Cocoa Helps You Create Great Apps for OS X 7
Common Behaviors Make Apps Complete 8
Get It Right: Meet System and App Store Requirements 8
Finish Your App with Performance Tuning 8
How to Use This Document 9
See Also 9
The Mac Application Environment 10
An Environment Designed for Ease of Use 10
A Sophisticated Graphics Environment 11
Low-Level Details of the Runtime Environment 12
Based on UNIX 12
Concurrency and Threading 12
The File System 13
Security 18
The Core App Design 21
Fundamental Design Patterns 21
The App Style Determines the Core Architecture 23
The Core Objects for All Cocoa Apps 26
Additional Core Objects for Multiwindow Apps 29
Integrating iCloud Support Into Your App 30
Shoebox-Style Apps Should Not Use NSDocument 31
Document-Based Apps Are Based on an NSDocument Subclass 31
Documents in OS X 31
The Document Architecture Provides Many Capabilities for Free 32
The App Life Cycle 33
The main Function is the App Entry Point 33
The App’s Main Event Loop Drives Interactions 34
Automatic and Sudden Termination of Apps Improve the User Experience 36
Support the Key Runtime Behaviors in Your Apps 36
Automatic Termination 37
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
2Sudden Termination 38
User Interface Preservation 39
Apps Are Built Using Many Different Pieces 43
The User Interface 44
Event Handling 45
Graphics, Drawing, and Printing 46
Text Handling 47
Implementing the Application Menu Bar 47
Xcode Templates Provide the Menu Bar 48
Connect Menu Items to Your Code or Your First Responder 48
Implementing the Full-Screen Experience 49
Full-Screen API in NSApplication 49
Full-Screen API in NSWindow 50
Full-Screen API in NSWindowDelegate Protocol 50
Supporting Common App Behaviors 53
You Can Prevent the Automatic Relaunch of Your App 53
Making Your App Accessible Enables Many Users 53
Provide User Preferences for Customization 56
Integrate Your App With Spotlight Search 57
Use Services to Increase Your App’s Usefulness 58
Optimize for High Resolution 58
Think About Points, Not Pixels 58
Provide High-Resolution Versions of Graphics 59
Use High-Resolution-Savvy Image-Loading Methods 60
Use APIs That Support High Resolution 60
Prepare for Fast User Switching 61
Take Advantage of the Dock 62
Build-Time Configuration Details 63
Configuring Your Xcode Project 63
The Information Property List File 64
The OS X Application Bundle 66
Internationalizing Your App 69
Tuning for Performance and Responsiveness 71
Speed Up Your App’s Launch Time 71
Delay Initialization Code 71
Simplify Your Main Nib File 72
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
3
ContentsMinimize Global Variables 72
Minimize File Access at Launch Time 73
Don’t Block the Main Thread 73
Decrease Your App’s Code Size 73
Compiler-Level Optimizations 74
Use Core Data for Large Data Sets 75
Eliminate Memory Leaks 75
Dead Strip Your Code 75
Strip Symbol Information 76
Document Revision History 77
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
4
ContentsFigures, Tables, and Listings
The Mac Application Environment 10
Table 1-1 Key directories for Mac apps 14
Table 1-2 Attributes for the OS X file system 17
Listing 1-1 Getting the path to the Application Support directory 16
The Core App Design 21
Figure 2-1 The Calculator single-window utility app 24
Figure 2-2 The iPhoto single-window app 25
Figure 2-3 TextEdit document window 26
Figure 2-4 Key objects in a single-window app 27
Figure 2-5 Key objects in a multiwindow document app 29
Figure 2-6 Document file, object, and data model 32
Figure 2-7 The main event loop 35
Figure 2-8 Responder objects targeted by Cocoa for preservation 40
Figure 2-9 Windows and menus in an app 44
Figure 2-10 Processing events in the main run loop 45
Table 2-1 Fundamental design patterns used by Mac apps 21
Table 2-2 The core objects used by all Cocoa apps 27
Table 2-3 Additional objects used by multiwindow document apps 30
Listing 2-1 The main function of a Mac app 34
Listing 2-2 Returning the main window for a single-window app 43
Implementing the Full-Screen Experience 49
Table 3-1 Window delegate methods supporting full-screen mode 51
Supporting Common App Behaviors 53
Figure 4-1 Universal Access system preference dialog 55
Figure 4-2 Spotlight extracting metadata 57
Figure 4-3 Content appears the same size at standard resolution and high resolution 59
Build-Time Configuration Details 63
Figure 5-1 The information property list editor 65
Figure 5-2 The Language preference view 70
Table 5-1 A typical application bundle 66
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
5Tuning for Performance and Responsiveness 71
Table 6-1 Compiler optimization options 74
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
6
Figures, Tables, and ListingsThis document is the starting point for learning how to create Mac apps. It contains fundamental information
about the OS X environment and how your apps interact with that environment. It also contains important
information about the architecture of Mac apps and tips for designing key parts of your app.
At a Glance
Cocoa is the application environment that unlocks the full power of OS X. Cocoa provides APIs, libraries, and
runtimes that help you create fast, exciting apps that automatically inherit the beautiful look and feel of OS X,
as well as standard behaviors users expect.
Cocoa Helps You Create Great Apps for OS X
You write apps for OS X using Cocoa, which provides a significant amount of infrastructure for your program.
Fundamental design patterns are used throughout Cocoa to enable your app to interface seamlessly with
subsystem frameworks, and core application objects provide key behaviorsto supportsimplicity and extensibility
in app architecture. Key parts of the Cocoa environment are designed particularly to support ease of use, one
of the most important aspects of successful Mac apps. Many apps should adopt iCloud to provide a more
coherent user experience by eliminating the need to synchronize data explicitly between devices.
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
7
About OS X App DesignRelevant Chapters: “The Mac Application Environment” (page 10), “The Core App Design” (page
21), and “Integrating iCloud Support Into Your App” (page 30)
Common Behaviors Make Apps Complete
During the design phase of creating your app, you need to think about how to implement certain features
that users expect in well-formed Mac apps. Integrating these features into your app architecture can have an
impact on the user experience: accessibility, preferences, Spotlight, services, resolution independence, fast
user switching, and the Dock. Enabling your app to assume full-screen mode, taking over the entire screen,
provides users with a more immersive, cinematic experience and enables them to concentrate fully on their
content without distractions.
Relevant Chapters: “Supporting Common App Behaviors” (page 53) and “Implementing the
Full-Screen Experience” (page 49)
Get It Right: Meet System and App Store Requirements
Configuring your app properly is an important part of the development process. Mac apps use a structured
directory called a bundle to manage their code and resource files. And although most of the files are custom
and exist to support your app, some are required by the system or the App Store and must be configured
properly. The application bundle also contains the resources you need to provide to internationalize your app
to support multiple languages.
Relevant Chapter: “Build-Time Configuration Details” (page 63)
Finish Your App with Performance Tuning
As you develop your app and your project code stabilizes, you can begin performance tuning. Of course, you
want your app to launch and respond to the user’s commands as quickly as possible. A responsive app fits
easily into the user’s workflow and gives an impression of being well crafted. You can improve the performance
of your app by speeding up launch time and decreasing your app’s code footprint.
About OS X App Design
At a Glance
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
8Relevant Chapter: “Tuning for Performance and Responsiveness” (page 71)
How to Use This Document
This guide introduces you to the most important technologies that go into writing an app. In this guide you
will see the whole landscape of what's needed to write one. That is, this guide shows you all the "pieces" you
need and how they fit together. There are important aspects of app design that this guide does not cover,
such as user interface design. However, this guide includes many links to other documents that provide details
about the technologies it introduces, as well as links to tutorials that provide a hands-on approach.
In addition, this guide emphasizes certain technologies introduced in OS X v10.7, which provide essential
capabilities that set your app apart from older ones and give it remarkable ease of use, bringing some of the
best features from iOS to OS X.
See Also
The following documents provide additional information about designing Mac apps, as well as more details
about topics covered in this document:
● To work through a tutorial showing you how to create a Cocoa app, see Start Developing Mac Apps Today .
● For information about user interface design enabling you to create effective apps using OS X, see OS X
Human Interface Guidelines.
● To understand how to create an explicit app ID, create provisioning profiles, and enable the correct
entitlementsfor your application,so you can sell your application through the Mac App Store or use iCloud
storage, see Tools Workflow Guide for Mac .
● For information about the design patterns used in Cocoa, see Cocoa Fundamentals Guide .
● For a general survey of OS X technologies, see Mac Technology Overview.
● To understand how to implement a document-based app, see Document-Based App Programming Guide
for Mac .
About OS X App Design
How to Use This Document
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
9OS X incorporates the latest technologies for creating powerful and fun-to-use apps. But the technologies by
themselves are not enough to make every app great. What sets an app apart from its peers is how it helps the
user achieve some tangible goal. After all, users are not going to care what technologies an app uses, as long
as it helps them do what they need to do. An app that gets in the user’s way is going to be forgotten, but one
that makes work (or play) easier and more fun is going to be remembered.
You use Cocoa to write apps for OS X. Cocoa gives you access to all of the features of OS X and allows you to
integrate your app cleanly with the rest of the system. This chapter covers the key parts of OS X that help you
create great apps. In particular, this chapter describessome of the important ease-of-use technologiesintroduced
in OS X v10.7. For a more thorough list of technologies available in OS X, see Mac Technology Overview.
An Environment Designed for Ease of Use
OS X strives to provide an environment that is transparent to users and as easy to use as possible. By making
hard tasks simple and getting out of the way, the system makes it easier for the user to be creative and spend
less time worrying about the steps needed to make the computer work. Of course, simplifying tasks means
your app has to do more of the work, but OS X provides help in that respect too.
As you design your app, you should think about the tasks that users normally perform and find ways to make
them easier. OS X supports powerful ease-of-use features and design principles. For example:
● Users should not have to save their work manually. The document model in Cocoa provides support for
saving the user’sfile-based documents without user interaction;see “The Document Architecture Provides
Many Capabilities for Free” (page 32).
● Apps should restore the user’s work environment at login time. Cocoa provides support for archiving the
current state of the app’s interface (including the state of unsaved documents) and restoring that state at
launch time; see “User Interface Preservation” (page 39).
● Appsshould support automatic termination so that the user never hasto quit them. Automatic termination
means that when the user closes an app’s windows, the app appears to quit but actually just moves to
the background quietly. The advantage is that subsequent launches are nearly instant as the app simply
moves back to the foreground; see “Automatic and Sudden Termination of Apps Improve the User
Experience” (page 36)
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
10
The Mac Application Environment● You should consider providing your users with an immersive, full-screen experience by implementing a
full-screen version of your user interface. The full-screen experience eliminates outside distractions and
allows the user to focus on their content; see “Implementing the Full-Screen Experience” (page 49).
● Support trackpad gestures for appropriate actions in your app. Gestures provide simple shortcuts for
common tasks and can be used to supplement existing controls and menu commands. OS X provides
automatic support for reporting gestures to your app through the normal event-handling mechanism;
see Cocoa Event Handling Guide .
● Consider minimizing or eliminating the user’s interactions with the raw file system. Rather than expose
the entire file system to the user through the open and save panels, some apps, in the manner of iPhoto
and iTunes, can provide a better user experience by presenting the user’s content in a simplified browser
designed specifically for the app’s content. OS X uses a well-defined file system structure that allows you
to place and find files easily and includes many technologies for accessing those files; see “The File
System” (page 13).
● For apps that support custom document types, provide a Quick Look plug-in so that users can view your
documents from outside of your app; see Quick Look Programming Guide .
● Apps should support the fundamental features for the OS X user experience that make apps elegant and
intuitive,such as direct manipulation and drag-and-drop. Usersshould remain in control, receive consistent
feedback, and be able to explore because the app is forgiving with reversible actions; see OS X Human
Interface Guidelines.
All of the preceding features are supported by Cocoa and can be incorporated with relatively little effort.
A Sophisticated Graphics Environment
High-quality graphics and animation make your app look great and can convey a lot of information to the user.
Animations in particular are a great way to provide feedback about changes to your user interface. So as you
design your app, keep the following ideas in mind:
● Use animations to provide feedback and convey changes. Cocoa provides mechanisms for creating
sophisticated animations quickly in both the AppKit and Core Animation frameworks. For information
about creating view-based animations, see Cocoa Drawing Guide . For information about using Core
Animation to create your animations, see Core Animation Programming Guide .
●
Include high-resolution versions of your art and graphics. OS X automatically loads high-resolution image
resources when an app runs on a screen whose scaling factor is greater than 1.0. Including such image
resources makes your app’s graphics look even sharper and crisper on those higher-resolution screens.
Forinformation aboutthe graphicstechnologies available inOS X,see “Media Layer” in Mac TechnologyOverview.
The Mac Application Environment
A Sophisticated Graphics Environment
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
11Low-Level Details of the Runtime Environment
When you are ready to begin writing actual code, there are a lot of technologies available to make your life
easier. OS X supports all of the basic features such as memory management, file management, networking,
and concurrency that you need to write your code. In some cases, though, OS X also provides more sophisticated
services (or specific coding conventions) that, when followed, can make writing your code even easier.
Based on UNIX
OS X is powered by a 64-bit Mach kernel, which manages processor resources, memory, and other low-level
behaviors. On top of the kernel sits a modified version of the Berkeley Software Distribution (BSD) operating
system, which provides interfaces that apps can use to interact with the lower-level system. This combination
of Mach and BSD provides the following system-level support for your apps:
● Preemptive multitasking—All processes share the CPU efficiently. The kernel schedules processes in a
way that ensures they all receive the time they need to run. Even background apps continue to receive
CPU time to execute ongoing tasks.
● Protected memory—Each process runs in its own protected memory space, which prevents processes
from accidentally interfering with each other. (Apps can share part of their memory space to implement
fast interprocess communication but take responsibility for synchronizing and locking that memory
appropriately.)
● Virtual memory—64-bit apps have a virtual address space of approximately 18 exabytes (18 billion billion
bytes). (If you create a 32-bit app, the amount of virtual memory is only 4 GB.) When an app’s memory
usage exceedsthe amount of free physical memory, the system transparently writes pagesto disk to make
more room. Written out pages remain on disk until they are needed in memory again or the app exits.
● Networking and Bonjour—OS X provides support for the standard networking protocols and services in
use today. BSD sockets provide the low-level communication mechanism for apps, but higher-level
interfaces also exist. Bonjour simplifies the user networking experience by providing a dynamic way to
advertise and connect to network services over TCP/IP.
For detailed information about the underlying environment of OS X, see “Kernel and Device Drivers Layer” in Mac
Technology Overview.
Concurrency and Threading
Each process starts off with a single thread of execution and can create more threads as needed. Although you
can create threads directly using POSIX and other higher-level interfaces, for most types of work it is better to
create them indirectly using block objects with Grand Central Dispatch (GCD) or operation objects, a Cocoa
concurrency technology implemented by the NSOperation class.
The Mac Application Environment
Low-Level Details of the Runtime Environment
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
12GCD and operation objects are an alternative to raw threads that simplify or eliminate many of the problems
normally associated with threaded programming,such assynchronization and locking. Specifically, they define
an asynchronous programming model in which you specify only the work to be performed and the order in
which you want it performed. The system then handles the tedious work required to schedule the necessary
threads and execute your tasks as efficiently as possible on the current hardware. You should not use GCD or
operations for work requiring time-sensitive data processing (such as audio or video playback), but you can
use them for most other types of tasks.
For more information on using GCD and operation objects to implement concurrency in your apps, see
Concurrency Programming Guide .
The File System
The file system in OS X is structured to provide a better experience for users. Rather than exposing the entire
file system to the user, the Finder hides any files and directories that an average user should not need to use,
such as the contents of low-level UNIX directories. This is done to provide a simpler interface for the end user
(and only in places like the Finder and the open and save panels). Apps can still access any files and directories
for which they have valid permissions, regardless of whether they are hidden by the Finder.
When creating apps, you should understand and follow the conventions associated with the OS X file system.
Knowing where to put files and how to get information out of the file system ensures a better user experience.
A Few Important App Directories
The OS X file system is organized in a way that groups related files and data together in specific places. Every
file in the file system has its place and apps need to know where to put the files they create. This is especially
important if you are distributing your app through the App Store, which expects you to put your app’s data
files in specific directories.
Table 1-1 lists the directories with which apps commonly interact. Some of these directories are inside the
home directory, which is either the user’s home directory or, if the app adopts App Sandbox, the app’s container
directory as described in “App Sandbox and XPC” (page 18). Because the actual paths can differ based on
these conditions, use the URLsForDirectory:inDomains: method of the NSFileManager classto retrieve
the actual directory path. You can then add any custom directory and filename information to the returned
URL object to complete the path.
The Mac Application Environment
Low-Level Details of the Runtime Environment
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
13Table 1-1 Key directories for Mac apps
Directory Description
Thisisthe installation directory for your app bundle. The path for the global Applications
directory is /Applications but each user directory may have a local applications
directory containing user-specific apps. Regardless, you should not need to use this
path directly. To access resources inside your application bundle, use an NSBundle
object instead.
For more information about the structure of your application bundle and how you
locate resources, see “The OS X Application Bundle” (page 66).
Applications
directory
The configuration of your app determines the location of the home directory seen by
your app:
● For apps running in a sandbox in OS X v10.7 and later, the home directory is the
app’s container directory. For more information about the container directory,
see “The Keychain” (page 20).
● For apps running outside of a sandbox (including those running in versions of OS
X before 10.7), the home directory isthe user-specific subdirectory of /Users that
contains the user’s files.
To retrieve the path to the home directory, use the NSHomeDirectory function.
Home
directory
The Library directory is the top-level directory for storing private app-related data and
preferences. There are several Library directories scattered throughout the system but
you should always use the one located inside the current home directory.
Do not store files directly at the top-level of the Library directory. Instead, store them
in one of the specific subdirectories described in this table.
In OS X v10.7 and later, the Finder hides the Library directory in the user’s home folder
by default. Therefore, you should never store files in this directory that you want the
user to access.
To get the path to this directory use the NSLibraryDirectory search path key with
the NSUserDomainMask domain.
Library
directory
The Mac Application Environment
Low-Level Details of the Runtime Environment
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
14Directory Description
The Application Support directory is where your app stores any type of file thatsupports
the app but is not required for the app to run, such as document templates or
configuration files. The files should be app-specific but should never store user data.
This directory is located inside the Library directory.
Never store files at the top level of this directory: Always put them in a subdirectory
named for your app or company.
If the resources apply to all users on the system, such as document templates, place
them in /Library/Application Support. To get the path to this directory use
the NSApplicationSupportDirectory search path key with the
NSLocalDomainMask domain. If the resources are user-specific, such as workspace
configuration files, place them in the current user’s ~/Library/Application
Support directory. To get the path to this directory use the
NSApplicationSupportDirectory search path key with the NSUserDomainMask
domain.
Application
Support
directory
The Caches directory is where you store cache files and other temporary data that your
app can re-create as needed. This directory is located inside the Library directory.
Never store files at the top level of this directory: Always put them in a subdirectory
named for your app or company. Your app is responsible for cleaning out cache data
files when they are no longer needed. The system does not delete files from this
directory.
To get the path to this directory use the NSCachesDirectory search path key with
the NSUserDomainMask domain.
Caches
directory
The Movies directory contains the user’s video files.
To get the path to this directory use the NSMoviesDirectory search path key with
the NSUserDomainMask domain.
Movies
directory
The Music directory contains the user’s music and audio files.
To get the path to this directory use the NSMusicDirectory search path key with
the NSUserDomainMask domain.
Music
directory
The Pictures directory contains the user’s images and photos.
To get the path to this directory use the NSPicturesDirectory search path key
with the NSUserDomainMask domain.
Pictures
directory
The Mac Application Environment
Low-Level Details of the Runtime Environment
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
15Directory Description
The Temporary directory is where you store files that do not need to persist between
launches of your app. You normally use this directory for scratch files or other types
ofshort-lived data filesthat are not related to your app’s persistent data. This directory
is typically hidden from the user.
Your app should remove files from this directory as soon as it is done with them. The
system may also purge lingering files from this directory at system startup.
To get the path to this directory use the NSTemporaryDirectory function.
Temporary
directory
Listing 1-1 shows an example of how to retrieve the base path to the Application Support directory and
then append a custom app directory to it.
Listing 1-1 Getting the path to the Application Support directory
NSFileManager* fileManager = [NSFileManager defaultManager];
NSURL* appSupportDir = nil;
NSArray *urls = [fileManager URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask];
if ([paths count] > 0) {
appSupportDir = [[urls objectAtIndex:0]
URLByAppendingPathComponent:@"com.example.MyApp"];
}
For more information about how to access files in well known system directories, see File System Programming
Guide .
Coordinating File Access with Other Processes
In OS X, other processes may have access to the same files that your app does. Therefore, when working with
files, you should use the file coordination interfacesintroduced in OS X v10.7 to be notified when other processes
(including the Finder) attempt to read or modify files your app is currently using. For example, coordinating
file access is critical when your app adopts iCloud storage.
The file coordination APIs allow you to assert ownership over files and directories that your app cares about.
Any time another process attempts to touch one of those items, your app is given a chance to respond. For
example, when an app attemptsto read the contents of a document your app is editing, you can write unsaved
changes to disk before the other process is allowed to do its reading.
The Mac Application Environment
Low-Level Details of the Runtime Environment
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
16Using iCloud document storage, for example, you must incorporate file coordination because multiple apps
can access your document files in iCloud. The simplest way to incorporate file coordination into your app is to
use the NSDocument class, which handles all of the file-related management for you. See Document-Based
App Programming Guide for Mac .
On the other hand, if you're writing a library-style (or “shoebox”) app, you must use the file coordination
interfaces directly, as described in File System Programming Guide .
Interacting with the File System
Disks in Macintosh computers are formatted using the HFS+ file system by default. However, Macintosh
computers can interact with disks that use other formats so you should never code specifically to any one file
system. Table 1-2 lists some of the basic file system attributes you may need to consider in your app and how
you should handle them.
Table 1-2 Attributes for the OS X file system
Attribute Description
The HFS+ file system is case-insensitive but also case-preserving. Therefore, when
specifying filenames and directoriesin your code, it is best to assume case-sensitivity.
Case sensitivity
Construct paths using the methods of the NSURL and NSString classes. The NSURL
classis preferred for path construction because of its ability to specify not only paths
in the local file system but paths to network resources.
Path
construction
Many file-related attributes can be retrieved using the getResourceValue:
forKey:error: method of the NSURL class. You can also use an NSFileManager
object to retrieve many file-related attributes.
File attributes
File permissions are managed using access control lists(ACLs) and BSD permissions.
The system uses ACLs whenever possible to specify precise permissionsfor files and
directories, but it falls back to using BSD permissions when no ACLs are specified.
By default, any files your app creates are owned by the current user and given
appropriate permissions. Thus, your app should always be able to read and write
files it creates explicitly. In addition, the app’s sandbox may allow it to access other
filesin specific situations. For more information about the sandbox,see “App Sandbox
and XPC” (page 18).
File permissions
Appsthat cannot use the File Coordination interfaces(see “Coordinating File Access
with Other Processes” (page 16)) to track changes to files and directories can use
the FSEvents API instead. This API provides a lower-level interface for tracking file
system interactions and is available in OS X v10.5 and later.
For information on how to use the FSEvents API,see File SystemEvents Programming
Guide .
Tracking file
changes
The Mac Application Environment
Low-Level Details of the Runtime Environment
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
17Security
The security technologies in OS X help you safeguard sensitive data created or managed by your app, and help
minimize damage caused by successful attacks from hostile code. These technologies impact how your app
interacts with system resources and the file system.
App Sandbox and XPC
You secure your app against attack from malware by following the practices recommended in Secure Coding
Guide . But an attacker needs only to find a single hole in your defenses, or in any of the frameworks and libraries
that you link against, to gain control of your app along with all of its privileges.
Starting in OS X v10.7, App Sandbox provides a last line of defense against stolen, corrupted, or deleted user
data if malicious code exploits your app. App Sandbox also minimizes the damage from coding errors. Its
strategy is twofold:
1. App Sandbox enables you to describe how your app interacts with the system. The system then grants
your app the access it needs to get its job done, and no more. For your app to provide the highest level
of damage containment, the best practice is to adopt the tightest sandbox possible.
2. App Sandbox allows the user to transparently grant your app additional access by way of Open and Save
dialogs, drag and drop, and other familiar user interactions.
You describe your app’s interaction with the system by way of setting entitlements in Xcode. An entitlement
is a key-value pair, defined in a property list file, that confers a specific capability or security permission to a
target. For example, there are entitlement keys to indicate that your app needs access to the camera, the
network, and user data such as the Address Book. For details on all the entitlements available in OS X, see
Entitlement Key Reference .
When you adopt App Sandbox, the system provides a special directory for use by your app—and only by your
app—called a container. Your app has unfettered read/write access to the container. All OS X path-finding
APIs, above the POSIX layer, are relative to the container instead of to the user’s home directory. Othersandboxed
apps have no access to your app’s container, as described further in “Code Signing” (page 19).
The Mac Application Environment
Low-Level Details of the Runtime Environment
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
18iOS Note: Because it is not for user documents, an OS X container differs from an iOS container
which, in iOS, is the one and only location for user documents. As the sole local location for user
documents, an iOS container is usually known simply as an app’s Documents directory.
In addition, an iOS container contains the app itself. This is not so in OS X.
iCloud Note: Apple’s iCloud technology, as described in “iCloud Storage”, uses the name “container”
as well. There is no functional connection between an iCloud container and an App Sandbox container.
Your sandboxed app can access paths outside of its container in the following three ways:
● At the specific direction of the user
● By you configuring your app with entitlements for specific file-system locations, such as the Movies folder
● When a path is in any of certain directories that are world readable
The OS X security technology that interacts with the user to expand yoursandbox is called Powerbox. Powerbox
has no API. Your app uses Powerbox transparently when, for example, you use the NSOpenPanel and
NSSavePanel classes, or when the user employs drag and drop with your app.
Some app operations are more likely to be targets of malicious exploitation. Examples are the parsing of data
received over a network, and the decoding of video frames. By using XPC, you can improve the effectiveness
of the damage containment offered by App Sandbox by separating such potentially dangerous activities into
their own address spaces.
XPC is an OS X interprocess communication technology that complements App Sandbox by enabling privilege
separation. Privilege separation, in turn, is a development strategy in which you divide an app into pieces
according to the system resource access that each piece needs. The component pieces that you create are
called XPC services. For details on adopting XPC, see Daemons and Services Programming Guide .
For a complete explanation of App Sandbox and how to use it, read App Sandbox Design Guide .
Code Signing
OS X employs the security technology known as code signing to allow you to certify that your app was indeed
created by you. After an app is code signed, the system can detect any change to the app—whether the change
is introduced accidentally or by malicious code. Various security technologies, including App Sandbox and
parental controls, depend on code signing.
The Mac Application Environment
Low-Level Details of the Runtime Environment
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
19In most cases, you can rely on Xcode’s automatic code signing, which requires only that you specify a code
signing identity in the build settings for your project. The steps to take are described in “Code Signing Your App”
in Tools Workflow Guide for Mac . If you need to incorporate code signing into an automated build system, or
if you link your app against third-party frameworks, refer to the procedures described in Code Signing Guide .
When you adopt App Sandbox, you must code sign your app. This is because entitlements (including the
special entitlement that enables App Sandbox) are built into an app’s code signature.
OS X enforces a tie between an app’s container and the app’s code signature. This important security feature
ensures that no other sandboxed app can access your container. The mechanism works as follows: After the
system creates a container for an app, each time an app with the same bundle ID launches, the system checks
that the app’s code signature matches a code signature expected by the container. If the system detects a
mismatch, it prevents the app from launching.
For a complete explanation of code signing in the context of App Sandbox, read “App Sandbox in Depth” in App
Sandbox Design Guide .
The Keychain
A keychain is a secure, encrypted container for storing a user’s passwords and other secrets. It is designed to
help a user manage their multiple logins, each with its own ID and password. You should always use keychain
to store sensitive credentials for your app.
For more on the keychain, see “Keychain Services Concepts” in Keychain Services Programming Guide .
The Mac Application Environment
Low-Level Details of the Runtime Environment
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
20To unleash the power of OS X, you develop apps using the Cocoa application environment. Cocoa presents
the app’s user interface and integrates it tightly with the other components of the operating system. Cocoa
provides an integrated suite of object-oriented software components packaged in two core class libraries, the
AppKit and Foundation frameworks, and a number of underlying frameworks providing supporting technologies.
Cocoa classes are reusable and extensible—you can use them as is or extend them for your particular
requirements.
Cocoa makes it easy to create apps that adopt all of the conventions and expose all of the power of OS X. In
fact, you can create a new Cocoa application project in Xcode and, without adding any code, have a functional
app. Such an app is able to display its window (or create new documents) and implements many standard
system behaviors. And although the Xcode templates provide some code to make this all happen, the amount
of code they provide is minimal. Most of the behavior is provided by Cocoa itself.
To make a great app, you should build on the foundations Cocoa lays down for you, working with the
conventions and infrastructure provided for you. To do so effectively, it'simportant to understand how a Cocoa
app fits together.
Fundamental Design Patterns
Cocoa incorporates many design patterns in its implementation. Table 2-1 lists the key design patterns with
which you should be familiar.
Table 2-1 Fundamental design patterns used by Mac apps
Design pattern Why it is important
Use of the Model-View-Controller (MVC) design pattern ensures that the
objects you create now can be reused or updated easily in future versions of
your app.
Cocoa provides most of the classes used to build your app’s controller and
view layers. It is your job to customize the classes you need and provide the
necessary data model objects to go with them.
MVC is central to a good design for a Cocoa application because many Cocoa
technologies and architectures are based on MVC and require that your custom
objects assume one of the MVC roles.
Model-View-Controller
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
21
The Core App DesignDesign pattern Why it is important
The delegation design pattern allows you to change the runtime behavior of
an object without subclassing. Delegate objects conform to a specific protocol
that defines the interaction points between the delegate and the object it
modifies. Atspecific points, the master object callsthe methods of its delegate
to provide it with information or ask what to do. The delegate can then take
whatever actions are appropriate.
Delegation
The responder chain definesthe relationships between event-handling objects
in your app. As events arrive, the app dispatches them to the first responder
object for handling. If that object does not want the event, it passes it to the
next responder, which can either handle the event or send it to its next
responder, and so on up the chain.
Windows and views are the most common types of responder objects and are
always the first responders for mouse events. Other types of objects, such as
your app’s controller objects, may also be responders.
Responder chain
Controls use the target-action design pattern to notify your app of user
interactions. When the user interacts with a control in a predefined way (such
as by touching a button), the controlsends a message (the action) to an object
you specify (the target). Upon receiving the action message, the target object
can then respond in an appropriate manner.
Target-action
Block objects are a convenient way to encapsulate code and local stack
variablesin a form that can be executed later. Blocks are used in lieu of callback
functions by many frameworks and are also used in conjunction with Grand
Central Dispatch to perform tasks asynchronously.
For more information about using blocks, see Blocks Programming Topics.
Block objects
Notifications are used throughout Cocoa to deliver news of changes to your
app. Many objects send notifications at key moments in the object’s life cycle.
Intercepting these notifications gives you a chance to respond and add custom
behavior.
Notifications
KVO tracks changes to a specific property of an object. When that property
changes, the change generates automatic notifications for any objects that
registered an interest in that property. Those observers then have a chance to
respond to the change.
Key-value observing
(KVO)
The Core App Design
Fundamental Design Patterns
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
22Design pattern Why it is important
Cocoa bindings provide a convenient bridge between the model, view, and
controller portions of your app. You bind a view to some underlying data object
(which can be static or dynamic) through one of your controllers. Changes to
the view are then automatically reflected in the data object, and vice versa.
The use of bindings is not required for apps but does minimize the amount of
code you have to write. You can set up bindings programmatically or using
Interface Builder.
Bindings
For a more detailed discussion of Cocoa and the design patterns you use to implement Mac apps, see Cocoa
Fundamentals Guide .
The App Style Determines the Core Architecture
The style of your app defines which core objects you must use in its implementation. Cocoa supports the
creation of both single-window and multiwindow apps. For multiwindow designs, it also provides a document
architecture to help manage the files associated with each app window. Thus, apps can have the following
forms:
● Single-window utility app
● Single-window library-style app
● Multiwindow document-based app
You should choose a basic app style early in your design process because that choice affects everything you
do later. The single-window styles are preferred in many cases, especially for developers bringing apps from
iOS. The single-window style typically yields a more streamlined user experience, and it also makes it easier
for your app to support a full-screen mode. However, if your app works extensively with complex documents,
the multiwindow style may be preferable because it provides more document-related infrastructure to help
you implement your app.
The Core App Design
The App Style Determines the Core Architecture
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
23The Calculator app provided with OS X, shown in Figure 2-1, is an example of a single-window utility app.
Utility apps typically handle ephemeral data or manage system processes. Calculator does not create or deal
with any documents or persistent user data but simply processes numerical data entered by the user into the
text field in its single window, displaying the results of its calculations in the same field. When the user quits
the app, the data it processed is simply discarded.
Figure 2-1 The Calculator single-window utility app
Single-window, library-style (or “shoebox”) apps do handle persistent user data. One of the most prominent
examples of a library-style app is iPhoto, shown in Figure 2-2. The user data handled by iPhoto are photos (and
associated metadata), which the app edits, displays, and stores. All user interaction with iPhoto happens in a
single window. Although iPhoto stores its data in files, it doesn’t present the files to the user. The app presents
a simplified interface so that users don’t need to manage files in order to use the app. Instead, they work
directly with their photos. Moreover, iPhoto hides its files from regular manipulation in the Finder by placing
The Core App Design
The App Style Determines the Core Architecture
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
24them within a single package. In addition, the app savesthe user’s editing changesto disk at appropriate times.
So, users are relieved of the need to manually save, open, or close documents. This simplicity for users is one
of the key advantages of the library-style app design.
Figure 2-2 The iPhoto single-window app
A good example of a multiwindow document-based app is TextEdit, which creates, displays, and edits documents
containing plain or styled text and images. TextEdit does not organize or manage its documents—users do
that with the Finder. Each TextEdit document opens in its own window, multiple documents can be open at
The Core App Design
The App Style Determines the Core Architecture
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
25one time, and the user interacts with the frontmost document using controls in the window’s toolbar and the
app’s menu bar. Figure 2-3 shows a document created by TextEdit. For more information about the
document-based app design, see “Document-Based Apps Are Based on an NSDocument Subclass” (page 31).
Figure 2-3 TextEdit document window
Both single-window and multiwindow apps can present an effective full-screen mode, which provides an
immersive experience that enables users to focus on their tasks without distractions. For information about
full-screen mode, see “Implementing the Full-Screen Experience” (page 49).
The Core Objects for All Cocoa Apps
Regardless of whether you are using a single-window or multiwindow app style, all apps use the same core
set of objects. Cocoa provides the default behavior for most of these objects. You are expected to provide a
certain amount of customization of these objects to implement your app’s custom behavior.
The Core App Design
The App Style Determines the Core Architecture
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
26Figure 2-4 shows the relationships among the core objects for the single-window app styles. The objects in
this figure are separated according to whether they are part of the model, view, or controller portions of the app.
As you can see from the figure, the Cocoa–provided objects provide much of the controller and view layer for
your app.
Figure 2-4 Key objects in a single-window app
Table 2-2 (page 27) describes the roles played by the objects in the diagram.
Table 2-2 The core objects used by all Cocoa apps
Object Description
(Required) Runs the event loop and manage interactions between your app and
the system. You typically use the NSApplication class as is, putting any custom
app-object-related code in your application delegate object.
NSApplication
object
(Expected) A custom object that you provide which works closely with the
NSApplication object to run the app and manage the transitions between
different application states.
Your application delegate object must conform to the NSApplicationDelegate
Protocol.
Application
delegate object
The Core App Design
The App Style Determines the Core Architecture
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
27Object Description
Store content specific to your app. A banking app might store a database
containing financial transactions, whereas a painting app might store an image
object or the sequence of drawing commands that led to the creation of that
image.
Data model
objects
Responsible for loading and managing a single window each and coordinating
with the system to handle standard window behaviors.
You subclass NSWindowController tomanage both the window and its contents.
Each window controller is responsible for everything that happens in its window.
If the contents of your window are simple, the window controller may do all of
the management itself. If your window is more complex, the window controller
might use one or more view controllers to manage portions of the window.
Window
controllers
Represent your onscreen windows, configured in different styles depending on
your app’s needs. For example, most windows have title bars and borders but you
can also configure windows without those visual adornments. A window object
is almost always managed by a window controller.
An app can also have secondary windows, also known as dialogs and panels.
These windows are subordinate to the current document window or, in the case
of single-window apps, to the main window. They support the document or main
window, for example, allowing selection of fonts and color, allowing the selection
of tools from a palette, or displaying a warning‚ A secondary window is often
modal.
Window objects
Coordinate the loading of a single view hierarchy into your app. Use view
controllersto divide up the work for managing more sophisticated window layouts.
Your view controllers work together (with the window controller) to present the
window contents.
If you have developed iOS apps, be aware that AppKit view controllers play a less
prominent role than UIKit view controllers. In OS X, AppKit view controllers are
assistantsto the window controller, which is ultimately responsible for everything
that goes in the window. The main job of an AppKit view controller is to load its
view hierarchy. Everything else is custom code that you write.
View controllers
Define a rectangular region in a window, draw the contents of that region, and
handle events in that region. Views can be layered on top of each other to create
view hierarchies, whereby one view obscures a portion of the underlying view.
View objects
Representstandard system controls. These view subclasses provide standard visual
items such as buttons, text fields, and tables that you can use to build your user
interface. Although a few controls are used as is to present visual adornments,
most work with your code to manage user interactions with your app’s content.
Control objects
The Core App Design
The App Style Determines the Core Architecture
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
28Additional Core Objects for Multiwindow Apps
As opposed to a single-window app, a multiwindow app uses several windows to present its primary content.
The Cocoa support for multiwindow appsis built around a document-based model implemented by a subsystem
called the document architecture. In this model, each document object manages its content, coordinates the
reading and writing of that content from disk, and presents the content in a window for editing. All document
objects work with the Cocoa infrastructure to coordinate event delivery and such, but each document object
is otherwise independent of its fellow document objects.
Figure 2-5 shows the relationships among the core objects of a multiwindow document-based app. Many of
the same objects in this figure are identical to those used by a single-window app. The main difference is the
insertion of the NSDocumentController and NSDocument objects between the application objects and the
objects for managing the user interface.
Figure 2-5 Key objects in a multiwindow document app
Table 2-3 (page 30) describes the role of the inserted NSDocumentController and NSDocument objects.
(For information about the roles of the other objects in the diagram, see Table 2-2 (page 27).)
The Core App Design
The App Style Determines the Core Architecture
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
29Table 2-3 Additional objects used by multiwindow document apps
Object Description
The NSDocumentController class defines a high-level controller for creating
and managing all document objects. In addition to managing documents, the
document controller also manages many document-related menu items, such as
the Open Recent menu and the open and save panels.
Document
Controller object
The NSDocument class is the base class for implementing documents in a
multiwindow app. This class acts as the controller for the data objects associated
with the document. You define your own custom subclasses to manage the
interactions with your app’s data objects and to work with one or more
NSWindowController objectsto display the document contents on the screen.
Document object
Integrating iCloud Support Into Your App
No matter how you store your app’s data, iCloud is a convenient way to make that data available to all of a
user’s devices. To integrate iCloud into your app, you change where you store user files. Instead of storing
them in the user’s Home folder or in your App Sandbox container, you store them in special file system locations
known as ubiquity containers. A ubiquity containerserves asthe local representation of corresponding iCloud
storage. It is outside of your App Sandbox container, and so requires specific entitlements for your app to
interact with it.
In addition to a change in file system locations, your app design needs to acknowledge that your data model
is accessible to multiple processes. The following considerations apply:
● Document-based apps get iCloud support through the NSDocument class, which handles most of the
interactions required to manage the on-disk file packages that represent documents.
●
If you implement a custom data model and manage files yourself, you must explicitly use file coordination
to ensure that the changes you make are done safely and in concert with the changes made on the user’s
other devices. For details,see “The Role of File Coordinators and Presenters” in File System Programming Guide .
● For storing small amounts of data in iCloud, you use key-value storage. Use key-value storage for such
things as stocks or weather information, locations, bookmarks, a recent-documents list, settings and
preferences, and simple game state. Every iCloud app should take advantage of key-value storage. To
interact with key-value storage, you use the shared NSUbiquitousKeyValueStore object.
To learn how to adopt iCloud in your app, read iCloud Design Guide .
The Core App Design
The App Style Determines the Core Architecture
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
30Shoebox-Style Apps Should Not Use NSDocument
When implementing a single-window, shoebox-style (sometimes referred to as a “library” style) app, it is
sometimes better not to use NSDocument objectsto manage your content. The NSDocument class was designed
specifically for use in multiwindow document apps. Instead, use custom controller objects to manage your
data. Those custom controllers would then work with a view controller or your app’s main window controller
to coordinate the presentation of the data.
Although you normally use an NSDocumentController object only in multiwindow apps, you can subclass
it and use it in a single-window app to coordinate the Open Recent and similar behaviors. When subclassing,
though, you must override any methods related to the creation of NSDocument objects.
Document-Based Apps Are Based on an NSDocument Subclass
Documents are containers for user data that can be stored in files and iCloud. In a document-based design,
the app enables users to create and manage documents containing their data. One app typically handles
multiple documents, each in its own window, and often displays more than one document at a time. For
example, a word processor provides commands to create new documents, it presents an editing environment
in which the user enters text and embeds graphics into the document, it saves the document data to disk (and,
optionally, iCloud), and it provides other document-related commands, such as printing and version
management. In Cocoa, the document-based app design is enabled by the document architecture, which is
part of of the AppKit framework.
Documents in OS X
There are several waysto think of a document. Conceptually, a document is a container for a body of information
that can be named and stored in a disk file and in iCloud. In this sense, the document is not the same as the
file but is an object in memory that owns and manages the document data. To users, the document is their
information—such as text and graphics formatted on a page. Programmatically, a document is an instance of
a custom NSDocument subclass that knows how to represent internally persistent data that it can display in
windows. This document object knows how to read document data from a file and create an object graph in
The Core App Design
Document-Based Apps Are Based on an NSDocument Subclass
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
31memory for the document data model. It also knows how to handle the user’s editing commands to modify
the data model and write the document data back out to disk. So, the document object mediates between
different representations of document data, as shown in Figure 2-6.
Figure 2-6 Document file, object, and data model
Using iCloud, documents can be shared automatically among a user’s computers and iOS devices. Changes to
the document data are synchronized without user intervention. For information about iCloud, see “Integrating
iCloud Support Into Your App” (page 30).
The Document Architecture Provides Many Capabilities for Free
The document-based style of app is a design choice that you should consider when you design your app. If it
makes sense for your users to create multiple discrete sets of data, each of which they can edit in a graphical
environment and store in files or iCloud, then you certainly should plan to develop a document-based app.
The Cocoa document architecture provides a framework for document-based apps to do the following things:
● Create new documents. The first time the user chooses to save a new document, it presents a dialog
enabling the user to name and save the document in a disk file in a user-chosen location.
● Open existing documents stored in files. A document-based app specifies the types of document it can
read and write, as well as read-only and write-only types. It can represent the data of different types
internally and display the data appropriately. It can also close documents.
● Automatically save documents. Document-based apps can adopt autosaving in place, and its documents
are automatically saved at appropriate times so that the data the user sees on screen is effectively the
same as that saved on disk. Saving is done safely, so that an interrupted save operation does not leave
data inconsistent.
The Core App Design
Document-Based Apps Are Based on an NSDocument Subclass
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
32● Asynchronously read and write document data. Reading and writing are done asynchronously on a
background thread, so that lengthy operations do not make the app’s user interface unresponsive. In
addition, reads and writes are coordinated using NSFilePresenter protocol and NSFileCoordinator
class to reduce version conflicts. Coordinated reads and writes reduce version conflicts both among
different appssharing document data in localstorage and among different instances of an app on different
devices sharing document data via iCloud.
● Manage multiple versions of documents. Autosave creates versions at regular intervals, and users can
manually save a version whenever they wish. Users can browse versions and revert the document’s contents
to a chosen version using a Time Machine–like interface. The version browser is also used to resolve version
conflicts from simultaneous iCloud updates.
● Print documents. The print dialog and page setup dialog enable the user to choose various page layouts.
● Monitor and set the document’s edited status and validate menu items. To avoid automatic saving of
inadvertent changes, old files are locked from editing until explicitly unlocked by the user.
● Track changes. The document manages its edited status and implements multilevel undo and redo.
● Handle app and window delegation. Notifications are sent and delegate methods called at significant
lifecycle events, such as when the app terminates.
See Document-Based App Programming Guide for Mac for more detailed information about how to implement
a document-based app.
The App Life Cycle
The app life cycle is the progress of an app from its launch through its termination. Apps can be launched by
the user or the system. The user launches apps by double-clicking the app icon, using Launchpad, or opening
a file whose type is currently associated with the app. In OS X v10.7 and later, the system launches apps at user
login time when it needs to restore the user’s desktop to its previous state.
When an app is launched, the system creates a process and all of the normal system-related data structures
for it. Inside the process, it creates a main thread and uses it to begin executing your app’s code. At that point,
your app’s code takes over and your app is running.
The main Function is the App Entry Point
Like any C-based app, the main entry point for a Mac app at launch time is the main function. In a Mac app,
the main function is used only minimally. Its main job is to give control to the AppKit framework. Any new
project you create in Xcode comes with a default main function like the one shown in Listing 2-1. You should
normally not need to change the implementation of this function.
The Core App Design
The App Life Cycle
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
33Listing 2-1 The main function of a Mac app
#import
int main(int argc, char *argv[])
{
return NSApplicationMain(argc, (const char **) argv);
}
The NSApplicationMain function initializes your app and prepares it to run. As part of the initialization
process, this function does several things:
● Creates an instance of the NSApplication class. You can access this object from anywhere in your app
using the sharedApplication class method.
● Loads the nib file specified by the NSMainNibFile key in the Info.plist file and instantiates all of the
objects in that file. This is your app’s main nib file and should contain your application delegate and any
other critical objects that must be loaded early in the launch cycle. Any objects that are not needed at
launch time should be placed in separate nib files and loaded later.
● Calls the run method of your application object to finish the launch cycle and begin processing events.
By the time the run method is called, the main objects of your app are loaded into memory but the app is still
not fully launched. The run method notifies the application delegate that the app is about to launch, shows
the application menu bar, opens any files that were passed to the app, does some framework housekeeping,
and starts the event processing loop. All of this work occurs on the app’s main thread with one exception. Files
may be opened in secondary threads if the canConcurrentlyReadDocumentsOfType: class method of the
corresponding NSDocument object returns YES.
If your app preserves its user interface between launch cycles, Cocoa loads any preserved data at launch time
and uses it to re-create the windows that were open the last time your app was running. For more information
about how to preserve your app’s user interface, see “User Interface Preservation” (page 39).
The App’s Main Event Loop Drives Interactions
Asthe user interacts with your app, the app’s main event loop processesincoming events and dispatchesthem
to the appropriate objects for handling. When the NSApplication object is first created, it establishes a
connection with the system window server, which receives events from the underlying hardware and transfers
The Core App Design
The App Life Cycle
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
34them to the app. The app also sets up a FIFO event queue to store the events sent to it by the window server.
The main event loop isthen responsible for dequeueing and processing events waiting in the queue, asshown
in Figure 2-7.
Figure 2-7 The main event loop
The run method of the NSApplication object is the workhorse of the main event loop. In a closed loop, this
method executes the following steps until the app terminates:
1. Services window-update notifications, which results in the redrawing of any windows that are marked as
“dirty.”
2. Dequeues an event from its internal event queue using the
nextEventMatchingMask:untilDate:inMode:dequeue: method and converts the event data into
an NSEvent object.
3. Dispatchesthe event to the appropriate target object using the sendEvent: method of NSApplication.
When the app dispatches an event, the sendEvent: method uses the type of the event to determine the
appropriate target. There are two major types of input events: key events and mouse events. Key events are
sent to the key window—the window that is currently accepting key presses. Mouse events are dispatched
to the window in which the event occurred.
For mouse events, the window looks for the view in which the event occurred and dispatches the event to
that object first. Views are responder objects and are capable of responding to any type of event. If the view is
a control, it typically uses the event to generate an action message for its associated target.
The overall process for handling events is described in detail in Cocoa Event Handling Guide .
The Core App Design
The App Life Cycle
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
35Automatic and Sudden Termination of Apps Improve the User Experience
In OS X v10.7 and later, the use of the Quit command to terminate an app is diminished in favor of more
user-centric techniques. Specifically, Cocoa supports two techniques that make the termination of an app
transparent and fast:
● Automatic termination eliminates the need for users to quit an app. Instead, the system manages app
termination transparently behind the scenes, terminating apps that are not in use to reclaim needed
resources such as memory.
● Sudden termination allows the system to kill an app’s process immediately without waiting for it to
perform any final actions. The system uses this technique to improve the speed of operations such as
logging out of, restarting, or shutting down the computer.
Automatic termination and sudden termination are independent techniques, although both are designed to
improve the user experience of app termination. Although Apple recommendsthat appssupport both, an app
can support one technique and not the other. Apps that support both techniques can be terminated by the
system without the app being involved at all. On the other hand, if an app supports sudden termination but
not automatic termination, then it must be sent a Quit event, which it needs to process without displaying
any user interface dialogs.
Automatic termination transfers the job of managing processes from the user to the system, which is better
equipped to handle the job. Users do not need to manage processes manually anyway. All they really need is
to run apps and have those apps available when they need them. Automatic termination makes that possible
while ensuring that system performance is not adversely affected.
Apps must opt in to both automatic termination and sudden termination and implement appropriate support
for them. In both cases, the app must ensure that any user data is saved well before termination can happen.
And because the user does not quit an autoterminable app, such an app should also save the state of its user
interface using the built-in Cocoa support. Saving and restoring the interface state provides the user with a
sense of continuity between app launches.
For information on how to support for automatic termination in your app, see “Automatic Termination” (page
37). For information on how to support sudden termination, see “Sudden Termination” (page 38).
Support the Key Runtime Behaviors in Your Apps
No matter what style of app you are creating, there are specific behaviors that all apps should support. These
behaviors are intended to help users focus on the content they are creating rather than focus on app
management and other busy work that is not part of creating their content.
The Core App Design
Support the Key Runtime Behaviors in Your Apps
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
36Automatic Termination
Automatic termination is a feature that you must explicitly code for in your app. Declaring support for automatic
termination is easy, but apps also need to work with the system to save the current state of their user interface
so that it can be restored later as needed. The system can kill the underlying process for an auto-terminable
app at any time, so saving this information maintains continuity for the app. Usually, the system kills an app’s
underlying process some time after the user has closed all of the app’s windows. However, the system may
also kill an app with open windows if the app is not currently on screen, perhaps because the user hid it or
switched spaces.
To support automatic termination, you should do the following:
● Declare your app’s support for automatic termination, either programmatically or using an Info.plist
key.
● Support saving and restoring your window configurations.
● Save the user’s data at appropriate times.
● Single-window, library-style apps should implement strategies for saving data at appropriate
checkpoints.
● Multiwindow, document-based apps can use the autosaving and saveless documents capabilities in
NSDocument.
● Whenever possible, support sudden termination for your app as well.
Enabling Automatic Termination in Your App
Declaring support for automatic termination letsthe system know that itshould manage the actual termination
of your app at appropriate times. An app has two ways to declare its support for automatic termination:
●
Include the NSSupportsAutomaticTermination key (with the value YES) in the app’s Info.plist
file. This sets the app’s default support status.
● Use the NSProcessInfo classto declare support for automatic termination dynamically. Use thistechnique
to change the default support of an app that includes the NSSupportsAutomaticTermination key in
its Info.plist file.
Automatic Data-Saving Strategies Relieve the User
You should always avoid forcing the user to save changesto their data manually. Instead, implement automatic
data saving. For a multiwindow app based on NSDocument, automatic saving is as simple as overriding the
autosavesInPlace classmethod to return YES. Formore information,seeDocument-Based App Programming
Guide for Mac .
The Core App Design
Support the Key Runtime Behaviors in Your Apps
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
37For a single-window, library-style app, identify appropriate pointsin your code where any user-related changes
should be saved and write those changes to disk automatically. This benefits the user by eliminating the need
to think about manually saving changes, and when done regularly, it ensures that the user does not lose much
data if there is a problem.
Some appropriate times when you can save user data automatically include the following:
● When the user closes the app window or quits the app (applicationWillTerminate:)
● When the app is deactivated (applicationWillResignActive:)
● When the user hides your app (applicationWillHide:)
● Whenever the user makes a valid change to data in your app
The last item means that you have the freedom to save the user’s data at any time it makes sense to do so. For
example, if the user is editing fields of a data record, you can save each field value as it is changed or you can
wait and save all fields when the user displays a new record. Making these types of incremental changes ensures
that the data is always up-to-date but also requires more fine-grained management of your data model. In
such an instance, Core Data can help you make the changes more easily. For information about Core Data, see
Core Data Starting Point.
Sudden Termination
Sudden termination lets the system know that your app’s process can be killed directly without any additional
involvement from your app. The benefit of supporting sudden termination is that it lets the system close apps
more quickly, which is important when the user is shutting down a computer or logging out.
An app has two ways to declare its support for sudden termination:
●
Include the NSSupportsSuddenTermination key (with the value YES) in the app’s Info.plist file.
● Use the NSProcessInfo class to declare support for sudden termination dynamically. You can also use
this class to change the default support of an app that includes the NSSupportsSuddenTermination
key in its Info.plist file.
One solution is to declare global support for the feature globally and then manually override the behavior at
appropriate times. Because sudden termination means the system can kill your app at any time after launch,
you should disable it while performing actions that might lead to data corruption if interrupted. When the
action is complete, reenable the feature again.
The Core App Design
Support the Key Runtime Behaviors in Your Apps
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
38You disable and enable sudden termination programmatically using the disableSuddenTermination and
enableSuddenTerminationmethods ofthe NSProcessInfo class. Thesemethodsincrement and decrement
a counter, respectively, maintained by the process. When the value of this counter is 0, the process is eligible
for sudden termination. When the value is greater than 0, sudden termination is disabled.
Enabling and disabling sudden termination dynamically also meansthat your app should save data progressively
and not rely solely on user actions to save important information. The best way to ensure that your app’s
information is saved at appropriate times is to support the interfaces in OS X v10.7 for saving your document
and window state. Those interfaces facilitate the automatic saving of relevant user and app data. For more
information about saving your user interface state, see “User Interface Preservation” (page 39). For more
information about saving your documents, see “Document-Based Apps Are Based on an NSDocument
Subclass” (page 31).
For additional information about enabling and disabling sudden termination,see NSProcessInfo Class Reference .
User Interface Preservation
The Resume feature, in OS X v10.7 and later, saves the state of your app’s windows and restores them during
subsequent launches of your app. Saving the state of your windows enables you to return your app to the
state it was in when the user last used it. Use the Resume feature especially if your app supports automatic
termination, which can cause your app to be terminated while it is running but hidden from the user. If your
app supports automatic termination but does not preserve its interface, the app launches into its default state.
Users who only switched away from your app might think that the app crashed while it was not being used.
Writing Out the State of Your Windows and Custom Objects
You must do the following to preserve the state of your user interface:
● For each window, you must set whether the window should be preserved using the setRestorable:
method.
● For each preserved window, you must specify an object whose job is to re-create that window at launch
time.
● Any objects involved in your user interface must write out the data they require to restore their state later.
● At launch time, you must use the provided data to restore your objects to their previous state.
The actual process of writing out your application state to disk and restoring it later is handled by Cocoa, but
you must tell Cocoa what to save. Your app’s windows are the starting point for all save operations. Cocoa
iterates over all of your app’s windows and saves data for the ones whose isRestorable method returns
YES. Most windows are preserved by default, but you can change the preservation state of a window using
the setRestorable: method.
The Core App Design
Support the Key Runtime Behaviors in Your Apps
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
39In addition to preserving your windows, Cocoa saves data for most of the responder objects associated with
the window. Specifically, it saves the views and window controller objects associated with the window. (For a
multiwindow document-based app, the window controller also saves data from its associated document
object.) Figure 2-8 shows the path that Cocoa takes when determining which objects to save. Window objects
are always the starting point, but other related objects are saved, too.
Figure 2-8 Responder objects targeted by Cocoa for preservation
All Cocoa window and view objects save basic information about their size and location, plus information
about other attributes that might affect the way they are currently displayed. For example, a tab view saves
the index of the selected tab, and a text view savesthe location and range of the current textselection. However,
these responder objects do not have any inherent knowledge about your app’s data structures. Therefore, it
is your responsibility to save your app’s data and any additional information needed to restore the window to
its current state. There are several places where you can write out your custom state information:
●
If you subclass NSWindow or NSView, implement the encodeRestorableStateWithCoder: method
in your subclass and use it to write out any relevant data.
Alternatively, your custom responder objects can override the restorableStateKeyPaths method and
use it to specify key paths for any attributes to be preserved. Cocoa uses the key paths to locate and save
the data for the corresponding attribute. Attributes must be compliant with key-value coding and Key-value
observing.
●
If your window has a delegate object, implement the window:willEncodeRestorableState: method
for the delegate and use it to store any relevant data.
●
In your window controller, use the encodeRestorableStateWithCoder: method to save any relevant
data or configuration information.
The Core App Design
Support the Key Runtime Behaviors in Your Apps
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
40Be judicious when deciding what data to preserve, and strive to write out the smallest amount of information
that is required to reconfigure your window and associated objects. You are expected to save the actual data
that the window displays and enough information to reattach the window to the same data objects later.
Important: Never use the user interface preservation mechanism as a way to save your app’s actual data.
The archive created for interface preservation can change frequently and may be ignored altogether if there
is a problem during the restoration process. Your app data should always be saved independently in data
files that are managed by your app.
For information on how to use coder objects to archive state information, see NSCoder Class Reference . For
additional information on what you need to do to save state in a multiwindow document-based app, see
Document-Based App Programming Guide for Mac .
Notifying Cocoa About Changes to Your Interface State
Whenever the preserved state of one of your responder objects changes, mark the object as dirty by calling the
invalidateRestorableState method of that object. Having done so, at some point in the future,
encodeRestorableStateWithCoder: message is sent to your responder object. Marking your responder
objects as dirty lets Cocoa know that it needs to write their preservation state to disk at an appropriate time.
Invalidating your objects is a lightweight operation in itself because the data is not written to disk right away.
Instead, changes are coalesced and written at key times, such as when the user switches to another app or
logs out.
You should mark a responder object as dirty only for changes that are truly interface related. For example, a
tab view marks itself as dirty when the user selects a different tab. However, you do not need to invalidate
your window or its views for many content-related changes, unless the content changes themselves caused
the window to be associated with a completely different set of data-providing objects.
If you used the restorableStateKeyPaths method to declare the attributes you want to preserve, Cocoa
preserves and restores the values of those attributes of your responder object. Therefore, any key paths you
provide should be key-value observing compliant and generate the appropriate notifications. For more information
on how to support key-value observing in your objects, see Key-Value Observing Programming Guide .
Restoring Your Windows and Custom Objects at Launch TIme
As part of your app’s normal launch cycle, Cocoa checks to see whether there is any preserved interface data.
If there is, Cocoa usesthat data to try to re-create your app’s windows. Every window must identify a restoration
class that knows about the window and can act on its behalf at launch time to create the window when asked
to do so by Cocoa.
The Core App Design
Support the Key Runtime Behaviors in Your Apps
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
41The restoration class is responsible for creating both the window and all of the critical objects required by that
window. For most app styles, the restoration class usually creates one or more controller objects as well. For
example, in a single-window app, the restoration class would likely create the window controller used to
manage the window and then retrieve the window from that object. Because it createsthese controller objects
too, you typically use high-level application classesfor your restoration classes. An app might use the application
delegate, a document controller, or even a window controller as a restoration class.
During the launch cycle, Cocoa restores each preserved window as follows:
1. Cocoa retrieves the window’s restoration class from the preserved data and calls its
restoreWindowWithIdentifier:state:completionHandler: class method.
2. The restoreWindowWithIdentifier:state:completionHandler: class method must call the
provided completion handler with the desired window object. To do this, it does one of the following:
●
It creates any relevant controller objects (including the window controller) that might normally be
created to display the window.
●
If the controller objects already exist (perhaps because they were already loaded from a nib file), the
method gets the window from those existing objects.
If the window could not be created, perhaps because the associated document was deleted by the user,
the restoreWindowWithIdentifier:state:completionHandler: should pass an error object to
the completion handler.
3. Cocoa uses the returned window to restore it and any preserved responder objects to their previous state.
● Standard Cocoa window and view objects are restored to their previousstate without additional help.
If you subclass NSWindow or NSView, implement the restoreStateWithCoder: method to restore
any custom state.
If you implemented the restorableStateKeyPaths method in your custom responder objects,
Cocoa automatically sets the value of the associated attributes to their preserved values. Thus, you
do not have to implement the restoreStateWithCoder: to restore these attributes.
● For the window delegate object, Cocoa calls the window:didDecodeRestorableState: method
to restore the state of that object.
● For your window controller, Cocoa calls the restoreStateWithCoder: method to restore its state.
When re-creating each window, Cocoa passes the window’s unique identifier string to the restoration class.
You are responsible for assigning user interface identifier strings to your windows prior to preserving the
window state. You can assign an identifier in your window’s nib file or by setting your window object's
identifier property (defined in NSUserInterfaceItemIdentification protocol). For example, you
might give your preferences window an identifier of preferences and then check for that identifier in your
The Core App Design
Support the Key Runtime Behaviors in Your Apps
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
42implementation. Your restoration class can use this identifier to determine which window and associated
objects it needs to re-create. The contents of an identifier string can be anything you want but should be
something to help you identify the window later.
For a single-window app whose main window controller and window are loaded from the main nib file, the
job of your restoration class is fairly straightforward. Here, you could use the application delegate’s class as
the restoration class and implement the restoreWindowWithIdentifier:state:completionHandler:
method similar to the implementation shown in Listing 2-2. Because the app has only one window, it returns
the main window directly. If you used the application delegate’s class asthe restoration classfor other windows,
your own implementation could use the identifier parameter to determine which window to create.
Listing 2-2 Returning the main window for a single-window app
+ (void)restoreWindowWithIdentifier:(NSString *)identifier
state:(NSCoder *)state
completionHandler:(void (^)(NSWindow *, NSError *))completionHandler
{
// Get the window from the window controller,
// which is stored as an outlet by the delegate.
// Both the app delegate and window controller are
// created when the main nib file is loaded.
MyAppDelegate* appDelegate = (MyAppDelegate*)[[NSApplication sharedApplication]
delegate];
NSWindow* mainWindow = [appDelegate.windowController window];
// Pass the window to the provided completion handler.
completionHandler(mainWindow, nil);
}
Apps Are Built Using Many Different Pieces
The objects of the core architecture are important but are not the only objects you need to consider in your
design. The core objects manage the high-level behavior of your app, but the objects in your app’s view layer
do most of the work to display your custom content and respond to events. Other objects also play important
roles in creating interesting and engaging apps.
The Core App Design
Apps Are Built Using Many Different Pieces
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
43The User Interface
An app’s user interface is made up of a menu bar, one or more windows, and one or more views. The menu
bar is a repository for commands that the user can perform in the app. Commands may apply to the app as a
whole, to the currently active window, or to the currently selected object. You are responsible for defining the
commands that your app supports and for providing the event-handling code to respond to them.
You use windows and views to present your app’s visual content on the screen and to manage the immediate
interactions with that content. A window is an instance of the NSWindow class. A panel is an instance of the
NSPanel class(which is a descendant of NSWindow) that you use to presentsecondary content. Single-window
apps have one main window and may have one or more secondary windows or panels. Multiwindow apps
have multiple windows for displaying their primary content and may have one or more secondary windows
or panels too. The style of a window determines its appearance on the screen. Figure 2-9 shows the menu bar,
along with some standard windows and panels.
Figure 2-9 Windows and menus in an app
A view, an instance of the NSView class, defines the content for a rectangular region of a window. Views are
the primary mechanism for presenting content and interacting with the user and have several responsibilities.
For example:
● Drawing and animation support. Views draw content in their rectangular area. Views that support Core
Animation layers can use those layers to animate their contents.
● Layout and subview management. Each view manages a list ofsubviews, allowing you to create arbitrary
view hierarchies. Each view defineslayout and resizing behaviorsto accommodate changesin the window
size.
The Core App Design
Apps Are Built Using Many Different Pieces
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
44● Event handling. Views receive events. Views forward events to other objects when appropriate.
For information about creating and configuring windows, see Window Programming Guide . For information
about using and creating view hierarchies, see View Programming Guide .
Event Handling
The system window server is responsible for tracking mouse, keyboard, and other events and delivering them
to your app. When the system launches an app, it creates both a process and a single thread for the app. This
initial thread becomes the app’s main thread. In it, the NSApplication object sets up the main run loop and
configures its event-handling code, as shown in Figure 2-10. As the window server delivers events, the app
queues those events and then processes them sequentially in the app’s main run loop. Processing an event
involves dispatching the event to the object best suited to handle it. For example, mouse events are usually
dispatched to the view in which the event occurred.
Figure 2-10 Processing events in the main run loop
Note: A run loop monitors sources of input on a specific thread of execution. The app’s event queue
represents one of these inputsources. While the event queue is empty, the main thread sleeps. When
an event arrives, the run loop wakes up the thread and dispatches control to the NSApplication
object to handle the event. After the event has been handled, control passes back to the run loop,
which can then process another event, process other input sources, or put the thread back to sleep
if there is nothing more to do. For more information about how run loops and input sources work,
see Threading Programming Guide .
Distributing and handling events is the job of responder objects, which are instances of the NSResponder
class. The NSApplication, NSWindow, NSDrawer, NSView, NSWindowController, and NSViewController
classes are all descendants of NSResponder. After pulling an event from the event queue, the app dispatches
The Core App Design
Apps Are Built Using Many Different Pieces
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
45that event to the window object where it occurred. The window object, in turn, forwards the event to its first
responder. In the case of mouse events, the first responder is typically the view object (NSView) in which the
touch took place. For example, a mouse event occurring in a button is delivered to the corresponding button
object.
If the first responder is unable to handle an event, it forwardsthe event to its nextresponder, which istypically
a parent view, view controller, or window. If that object is unable to handle the event, it forwards it to its next
responder, and so , until the event is handled. Thisseries of linked responder objectsis known asthe responder
chain. Messages continue traveling up the responder chain—toward higher-level responder objects, such as
a window controller or the application object—until the event is handled. If the event isn't handled, it is
discarded.
The responder object that handles an event often sets in motion a series of programmatic actions by the app.
For example, a control object (that is, a subclass of NSControl) handles an event by sending an action message
to another object, typically the controller that manages the current set of active views. While processing the
action message, the controller might change the user interface or adjust the position of views in ways that
require some of those views to redraw themselves. When this happens, the view and graphics infrastructure
takes over and processes the required redraw events in the most efficient manner possible.
For more information about responders, the responder chain, and handling events, see Cocoa Event Handling
Guide .
Graphics, Drawing, and Printing
There are two basic ways in which a Mac app can draw its content:
● Native drawing technologies (such as Core Graphics and AppKit)
● OpenGL
The native OS X drawing technologies typically use the infrastructure provided by Cocoa views and windows
to render and present custom content. When a view is first shown, the system asks it to draw its content.
System views draw their contents automatically, but custom views must implement a drawRect: method.
Inside this method, you use the native drawing technologies to draw shapes, text, images, gradients, or any
other visual content you want. When you want to update your view’s visual content, you mark all or part of
the view invalid by calling its setNeedsDisplay: or setNeedsDisplayInRect: method. The system then
calls your view’s drawRect: method (at an appropriate time) to accommodate the update. This cycle then
repeats and continues throughout the lifetime of your app.
If you are using OpenGL to draw your app’s content, you still create a window and view to manage your content,
but those objects simply provide the rendering surface for an OpenGL drawing context. Once you have that
drawing context, your app is responsible for initiating drawing updates at appropriate intervals.
The Core App Design
Apps Are Built Using Many Different Pieces
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
46For information about how to draw custom content in your views, see Cocoa Drawing Guide .
Text Handling
The Cocoa text system, the primary text-handling system in OS X, is responsible for the processing and display
of all visible text in Cocoa. It provides a complete set of high-quality typographical services through the
text-related AppKit classes, which enable apps to create, edit, display, and store text with all the characteristics
of fine typesetting.
The Cocoa text system provides all these basic and advanced text-handling features, and it also satisfies
additional requirements from the ever-more-interconnected computing world: support for the character sets
of all of the world’s living languages, powerful layout capabilities to handle various text directionality and
nonrectangular text containers, and sophisticated typesetting capabilities such as control of kerning, ligatures,
line breaking, and justification. Cocoa’s object-oriented textsystem is designed to provide all these capabilities
without requiring you to learn about or interact with more of the system than is necessary to meet the needs
of your app.
Underlying the Cocoa text system is Core Text, which provides low-level, basic text layout and font-handling
capabilities to higher-level engines such as Cocoa and WebKit. Core Text provides the implementation for
many Cocoa text technologies. App developers typically have no need to use Core Text directly. However, the
Core Text API is accessible to developers who must use it directly, such as those writing apps with their own
layout engine and those porting older ATSUI- or QuickDraw-based codebases to the modern world.
For more information about the Cocoa text system, see Cocoa Text Architecture Guide .
Implementing the Application Menu Bar
The classes NSMenu and NSMenuItem are the basis for all types of menus. An instance of NSMenu manages a
collection of menu items and draws them one beneath another. An instance of NSMenuItem represents a
menu item; it encapsulates all the information its NSMenu object needs to draw and manage it, but does no
drawing or event-handling itself. You typically use Interface Builder to create and modify any type of menu,
so often there is no need to write any code.
The application menu bar stretches across the top of the screen, replacing the menu bar of any other app
when the app is foremost. All of an app’s menus in the menu bar are owned by one NSMenu instance that’s
created by the app when it starts up.
The Core App Design
Implementing the Application Menu Bar
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
47Xcode Templates Provide the Menu Bar
Xcode’s Cocoa application templates provide that NSMenu instance in a nib file called MainMenu.xib. This
nib file contains an application menu (named with the app’s name), a File menu (with all of its associated
commands), an Edit menu (with text editing commands and Undo and Redo menu items), and Format, View,
Window, and Help menus (with their own menu items representing commands). These menu items, as well
as all of the menu items of the File menu, are connected to the appropriate first-responder action methods.
For example, the About menu item is connected to the orderFrontStandardAboutPanel: action method
in the File’s Owner that displays a standard About window.
The template has similar ready-made connections for the Edit, Format, View, Window, and Help menus. If your
app does not support any of the supplied actions (for example, printing), you should remove the associated
menu items (or menu) from the nib. Alternatively, you may want to repurpose and rename menu commands
and action methodsto suit your own app, taking advantage of the menu mechanism in the template to ensure
that everything is in the right place.
Connect Menu Items to Your Code or Your First Responder
For your app’s custom menu items that are not already connected to action methods in objects or placeholder
objects in the nib file, there are two common techniques for handling menu commands in a Mac app:
● Connect the corresponding menu item to a first responder method.
● Connect the menu item to a method of your custom application object or your application delegate object.
Of these two techniques, the first is more common given that many menu commands act on the current
document or its contents, which are part of the responder chain. The second technique is used primarily to
handle commands that are global to the app, such as displaying preferences or creating a new document. It
is possible for a custom application object or its delegate to dispatch events to documents, but doing so is
generally more cumbersome and prone to errors. In addition to implementing action methods to respond to
your menu commands, you must also implement the methods of the NSMenuValidation protocol to enable
the menu items for those commands.
Step-by-step instructions for connecting menu items to action methods in your code are given in “Designing
User Interfaces in Xcode”. For more information about menu validation and other menu topics, see Application
Menu and Pop-up List Programming Topics.
The Core App Design
Implementing the Application Menu Bar
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
48Enabling a window of your app to assume full-screen mode, taking over the entire screen, provides users with
a more immersive, cinematic experience. Full-screen appearance can be striking and can make your app stand
out. From a practical standpoint, full-screen mode presents a better view of users’ data, enabling them to
concentrate fully on their content without the distractions of other apps or the desktop.
In full-screen mode, by default, the menu bar and Dock are autohidden; that is, they are normally hidden but
reappear when the user moves the pointer to the top or bottom of the screen. A full-screen window does not
draw its titlebar and may have special handling for its toolbar.
The full-screen experience makes sense for many apps but not for all. For example, the Finder, Address Book,
and Calculator would not provide any benefit to users by assuming full-screen mode. The same is probably
true for most utility apps. Media-rich apps, on the other hand, can often benefit from full-screen presentation.
Beginning with OS X v10.7, Cocoa includes support for full-screen mode through APIs in NSApplication,
NSWindow, and NSWindowDelegate protocol. When the user chooses to enter full-screen mode, Cocoa
dynamically creates a space and puts the window into that space. This behavior enables the user to have one
window of an app running in full-screen mode in one space, while using other windows of that app, as well
as other apps, on the desktop in otherspaces. While in full-screen mode, the user can switch between windows
in the current app or switch apps.
Apps that have implemented full-screen user interfaces in previous versions of OS X should consider
standardizing on the full-screen APIs in OS X v10.7.
Full-Screen API in NSApplication
Full-screen support in NSApplication is provided by the presentation option
NSApplicationPresentationFullScreen. You can find the current presentation mode via the
NSApplication method currentSystemPresentationOptions, which is also key-value observable. You
can set the presentation options using the NSApplication method setPresentationOptions:. (Be sure
to observe the restrictions on presentation option combinations documented with
NSApplicationPresentationOptions, and set the presentation optionsin a try-catch block to ensure that
your program does not crash from an invalid combination of options.)
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
49
Implementing the Full-Screen ExperienceA window delegate may also specify that the window toolbar be removed from the window in full-screen
mode and be shown automatically with the menu bar by including
NSApplicationPresentationAutoHideToolbar in the presentation options returned from the
window:willUseFullScreenPresentationOptions: method of NSWindowDelegate.
Full-Screen API in NSWindow
The app must specify whether a given window can enter full-screen mode. Apps can set collection behavior
using the setCollectionBehavior: method by passing in various constants, and the current options may
be accessed via the collectionBehavior method. You can choose between two constants to override the
window collection behavior, as shown in the following table:
Constant Behavior
The frontmost window with this collection behavior becomes
the full-screen window. A window with this collection
behavior has a full-screen button in the upper right of its
titlebar.
NSWindowCollectionBehaviorFullScreenPrimary
Windows with this collection behavior can be shown in the
same space with the full-screen window.
NSWindowCollectionBehaviorFullScreenAuxiliary
When a window goesinto full-screen mode, the styleMask changesto NSFullScreenWindowMask to reflect
the state of the window.The setting of the styleMask goesthrough the setStyleMask: method. As a result,
a window can override this method if it has customization to do when entering or exiting full-screen.
A window can be taken into or out of full-screen mode using the toggleFullScreen: method. If an app
supports full-screen mode, it should add a menu item to the View menu with toggleFullScreen: as the
action, and nil as the target.
Full-Screen API in NSWindowDelegate Protocol
The following notifications are sent before and after the window enters and exits full-screen mode:
NSWindowWillEnterFullScreenNotification
NSWindowDidEnterFullScreenNotification
NSWindowWillExitFullScreenNotification
NSWindowDidExitFullScreenNotification
Implementing the Full-Screen Experience
Full-Screen API in NSWindow
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
50The window delegate has the following corresponding window delegate notification methods:
windowWillEnterFullScreen:
windowDidEnterFullScreen:
windowWillExitFullScreen:
windowDidExitFullScreen:
The NSWindowDelegate protocol methods supporting full-screen mode are listed in Table 3-1.
Table 3-1 Window delegate methods supporting full-screen mode
Method Description
Invoked to allow the delegate
to modify the full-screen
content size.
window: willUseFullScreenContentSize:
Returns the presentation
options the window will use
when transitioning to
full-screen mode.
window: willUseFullScreenPresentationOptions:
Invoked when the window is
about to enter full-screen mode.
The window delegate can
implement this method to
customize the animation by
returning a custom window or
array of windows containing
layers or other effects.
customWindowsToEnterFullScreenForWindow:
The system has started its
animation into full-screen
mode, including transitioning
into a new space. You can
implement this method to
perform custom animation with
the given duration to be in sync
with the system animation.
window:
startCustomAnimationToEnterFullScreenWithDuration:
Invoked if the window failed to
enter full-screen mode.
windowDidFailToEnterFullScreen:
Implementing the Full-Screen Experience
Full-Screen API in NSWindowDelegate Protocol
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
51Method Description
Invoked when the window is
about to exit full-screen mode.
The window delegate can
implement this method to
customize the animation when
the window is about to exit
full-screen by returning a
custom window or array of
windows containing layers or
other effects.
customWindowsToExitFullScreenForWindow:
The system has started its
animation out of full-screen,
including transitioning back to
the desktop space. You can
implement this method to
perform custom animation with
the given duration to be in sync
with the system animation.
window:
startCustomAnimationToExitFullScreenWithDuration:
Invoked if the window failed to
exit full-screen mode.
windowDidFailToExitFullScreen:
For more information about full-screen mode, see NSWindowDelegate Protocol Reference and the OS X Human
Interface Guidelines.
Implementing the Full-Screen Experience
Full-Screen API in NSWindowDelegate Protocol
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
52During the design phase of creating your app, you need to think about how to implement certain features
that users expect in well-formed Mac apps. Integrating these features into your app architecture can have an
impact on the data model or may require cooperation between significantly different portions in the app.
You Can Prevent the Automatic Relaunch of Your App
By default, as part of the Resume feature of OS X v10.7, apps that were open at logout are relaunched by the
system when the user logsin again. You can prevent the automatic relaunching of your app at login by sending
it a disableRelaunchOnLogin message. This NSApplication method increments a counter that controls
the app being relaunched; if the counter is 0 at the time the user logs out, then the app is relaunched when
the user logs back in. The counter is initially zero, providing the default relaunch behavior.
Your can reinstate automatic relaunching of your app by sending it an enableRelaunchOnLogin message.
This message decrements the relaunch counter, so an equal number of disableRelaunchOnLogin and
enableRelaunchOnLogin messages enables relaunching. Both methods are thread safe.
If your app should not be relaunched because it launches via some other mechanism, such as the launchd
system process, then the recommended practice is to send the app a disableRelaunchOnLogin message
once, and never pair it with an enableRelaunchOnLogin message.
If your app should not be relaunched because it triggers a restart (for example, if it is an installer), then the
recommended practice is to send it a disableRelaunchOnLogin message immediately before you attempt
to trigger a restart and send it an enableRelaunchOnLogin message immediately after. This procedure
handles the case where the user cancels restarting; if the user later restarts for another reason, then your app
should be relaunched.
Making Your App Accessible Enables Many Users
Millions of people have a disability or special need. These include visual and hearing impairments, physical
disabilities, and cognitive and learning challenges. Access to computers is vitally important for this population,
because computers can provide a level of independence that is difficult to attain any other way. As populations
around the world age, an increasing number of people will experience age-related disabilities, such as vision
or hearing loss. Current and future generations of the elderly will expect to be able to continue using their
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
53
Supporting Common App Behaviorscomputers and accessing their data, regardless of the state of their vision and hearing. Apps that support
customizable text displays, access by a screen reader, or the replacement of visual cues by audible ones can
serve this population well.
OS X is designed to accommodate assistive technologies and has many built-in features to help people with
disabilities. Users access most of this functionality through the Universal Access pane of System Preferences.
Some of these built-in technologies take advantage of the same accessibility architecture that allows external
assistive technologies to access your app. Designing your app with accessibility in mind not only allows you
to reach a larger group of users, it results in a better experience for all your users.
As a first step in designing your app, be sure to read OS X Human Interface Guidelines. That book provides
detailed specifications and best practices for designing and implementing an intuitive, consistent, and
aesthetically pleasing user interface that delivers the superlative experience Macintosh users have come to
expect. During the design process, you also should be aware of the accessibility perspective on many basic
design considerations. Consider the following basic accessibility-related design principles:
● Support full keyboard navigation. For many users, a mouse is difficult, if not impossible, to use.
Consequently, a user should be able to perform all your app’s functions using the keyboard alone.
● Don’t override built-in keyboard shortcuts. This applies both to the keyboard shortcuts OS X reserves
(listed in “Keyboard Shortcuts”) and to the accessibility-related keyboard shortcuts (listed in “Accessibility
Keyboard Shortcuts”). As a general rule, you should never override reserved keyboard shortcuts. In particular,
you should take care not to override accessibility-related keyboard shortcuts or your app will not be
accessible to users who enable full keyboard access.
A corollary to this principle is to avoid creating too many new keyboard shortcuts that are specific to your
app. Users should not have to memorize a new set of keyboard commands for each app they use.
● Provide alternatives for drag-and-drop operations. If your app relies on drag-and-drop operations in its
workflow, you should provide alternate ways to accomplish the same tasks. This may not be easy; in fact,
it may require the design of an alternate interface for apps that are heavily dependent on drag and drop.
● Make sure there’s always a way out of your app’s workflow. This is important for all users, of course,
but it’s essential for users of assistive technologies. A user relying on an assistive app to use your app may
have a somewhat narrower view of your app’s user interface. For this reason, it’s especially important to
make canceling operations and retracing steps easy.
In addition to the basic design principles, you should consider the requirements of specific disabilities and
resulting design solutions and adaptations you can implement. The main theme of these suggestions is to
provide as many alternate modes of content display as possible, so that users can find the way that suits their
needs best. Consider the following categories of disabilities:
Supporting Common App Behaviors
Making Your App Accessible Enables Many Users
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
54● Visual Disabilities. Visual disabilities include blindness, color blindness, and low vision. Make your app
accessible to assistive apps, such as screen readers. Ensure that color is not the only source of come
particular information in your user interface. Provide an audio option for all visual cues and feedback, as
well as succinct descriptions of images and animated content.
● Hearing Disabilities. When you design the audio output of your app, remember that some users may not
be able to hear your app’s sound effects well or at all. And, of course, there are situations in which any
user could not use audio output effectively, such as in a library. Providing redundant audio and visual
output can aid comprehension for users with other types of disabilities as well, such as cognitive and
learning disabilities.
● Motor and Cognitive Disabilities. People with motor disabilities may need to use alternatives to the
standard mouse and keyboard input devices. Other users may have difficulty with the fine motor control
required to double-click a mouse or to press key combinations on the keyboard. Users with cognitive or
learning disabilities may need extra time to complete tasks or respond to alerts.
OS X providessupport for many types of disabilities at the system level through solutions offered in the Universal
Access system preferences, illustrated in Figure 4-1. In addition, most standard Cocoa objects implement
accessibility through the NSAccessibility protocol, providing reasonable default behavior in most cases,
Cocoa apps built with standard objects are automatically accessible. In general, you need to explicitly implement
the NSAccessibility protocol methods only if you subclass one of them, adding new behavior.
Figure 4-1 Universal Access system preference dialog
Supporting Common App Behaviors
Making Your App Accessible Enables Many Users
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
55The NSAccessibility informal protocol defines methods that Cocoa classes must implement to make
themselves available to an external assistive app. An assistive app interacts with your app to allow persons
with disabilities to use your app. For example, a person with a visual impairment could use an app to convert
menu items and button labels into speech and then perform actions by verbal command.
An accessible object is described by a set of attributes that define characteristics such as the object type, its
value, its size and position on the screen, and its place in the accessibility hierarchy. For some objects, the set
of attributes can include parameterized attributes. Parameterized attributes behave similar to a function by
allowing you to pass a parameter when requesting an attribute value.
For more information, see Accessibility Overview for OS X .
Provide User Preferences for Customization
Preferences are settings that enable users to customize the appearance or behavior of your software. The OS
X user defaultssystem lets you access and manage user preferences. You can use the defaultssystem to provide
reasonable initial values for app settings, as well as save and retrieve the user's own preference selections
across sessions. The Cocoa NSUserDefaults class provides programmatic access to the user defaults system.
The NSUserDefaults class provides convenience methodsfor accessing common typessuch asfloats, doubles,
integers, Booleans, and URLs. A default object must be a property list, that is, an instance of (or for collections
a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you
want to store any other type of object, you should typically archive it to create an instance of NSData.
The user defaults system groups defaults into domains. Two of the domains are persistently saved in the user
defaults database: the app domain stores app-specific defaults, and the global domain stores defaults applicable
to all apps. In addition, the user defaults system provides three volatile domains whose values last only while
the user defaults object exists: the argument domain for defaults set from command-line arguments, the
languages domain containing defaults for a locale if one is specified, and the registration domain for “factory
defaults” registered by the app.
Your app uses the NSUserDefaults class to register default preferences, and typically it also provides a user
interface (a preferences panel) that enables the user to change those preferences. Preferences are stored in
the ~/Library/Preferences/ folder in a standard XML-format property list file as a set of key-value pairs.
The app-specific preferences property list is identified by the bundle identifier of the app.
For more details, see Preferences and Settings Programming Guide .
Supporting Common App Behaviors
Provide User Preferences for Customization
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
56Integrate Your App With Spotlight Search
Spotlight is a fast desktop search technology that allows users to organize and search for files based on
metadata. Metadata is data about a file, rather than the actual content stored in the file. Metadata can include
familiar information such as an asset’s author and modification date but it can also be keywords or other
information that is custom to a particular asset. For example, an image file might have metadata describing
the image’s dimensions and color model.
Developers of appsthatsave documentsto disk should consider providing Spotlightsupport by implementing
a metadata importer. A Spotlight metadata importer is a small plug-in bundle that you create to extract
information from files created by your app. Spotlight importers are used by the Spotlight server to gather
information about new and existing files, as illustrated in Figure 4-2.
Figure 4-2 Spotlight extracting metadata
Apple provides importers for many standard file types that the system uses, including RTF, JPEG, Mail, PDF and
MP3. However, if you define a custom document format, you must create a metadata importer for your own
content. Xcode provides a template for writing Spotlight importer plug-ins. For information about writing a
Spotlight importer, see Spotlight Importer Programming Guide .
In addition, you may wish to provide metadata search capability in your app. Cocoa provides the
NSMetadataQuery class which enables you to construct queries using a subset of the NSPredicate classes
and execute the queries asynchronously. For information about providing Spotlight search capability in your
app, see File Metadata Search Programming Guide .
For more information about Spotlight, see Spotlight Overview.
Supporting Common App Behaviors
Integrate Your App With Spotlight Search
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
57Use Services to Increase Your App’s Usefulness
Services allow a user to access the functionality of one app from within another app. An app that provides a
service advertises the operations it can perform on a particular type of data—for example, encryption of text,
optical character recognition of a bitmapped image, or generating text such as a message of the day. When
the user is manipulating that particular type of data in some app, the user can choose the appropriate item in
the Services menu to operate on the current data selection (or merely insert new data into the document).
See Services Implementation Guide for more information.
Optimize for High Resolution
High-resolution displays provide a rich visual experience, allowing users to see sharper text and more details
in photos than on standard-resolution displays. The high-resolution model for OS X enables your app to draw
into an abstract coordinate space called user space, without regard for the characteristics of the final drawing
destination—printer, display screen, bitmap, or PDF—and without regard to the resolution of the display.
OS X provides much support for high-resolution automatically. For example,standard AppKit views and controls
automatically render correctly at any resolution, vector-based content automatically uses additional pixels to
rendersharper lines and shapes, Cocoa text displayssharper in high-resolution, and AppKit automatically loads
high-resolution variants of your images.
You must do the following things to optimize your app for high-resolution:
● Provide properly-named high-resolution versions of your bitmapped images.
● Use high-resolution-savvy image-loading methods.
● Use the most recent APIs that support high resolution.
These techniques are described in the following sections.
Think About Points, Not Pixels
OS X refers to screen size in points, not pixels. A point is one unit in user space, prior to any transformations
on the space. Because, on a high-resolution display, there are four onscreen pixels for each point, points can
be expressed asfloating-point values. Valuesthat are integersin standard resolution,such as mouse coordinates,
are floating-point values on a high-resolution display, allowing for greater precision for such things as graphics
alignment.
Supporting Common App Behaviors
Use Services to Increase Your App’s Usefulness
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
58Your app draws to a view using points in user space. The window server composites drawing operations to an
offscreen buffer called the backing store. When it comes time to display the contents of the backing store
onscreen, the window server scales the content appropriately, mapping points to onscreen pixels. The result
is that if you draw the same content on two similar devices, and only one of them has a high-resolution screen,
the content appears to be about the same size on both devices, as shown in Figure 4-3.
Figure 4-3 Content appears the same size at standard resolution and high resolution
In some situations you may need to know how points are mapped to pixels, in which case you can obtain the
backing scale factor which is always either 1.0 or 2.0. The backing scale factor is a property of a layer, view,
or window, and it depends on the resolution of the underlying display device.
Provide High-Resolution Versions of Graphics
OS X automatically magnifies standard-resolution bitmapped images so they appear to the user in the correct
size, but they appear fuzzy. To avoid this problem, you must provide high-resolution versions of your graphics,
along with the standard-resolution versions in the app bundle. In addition to any images your app displays,
you must do this for your app’s icons and any custom controls, cursors, and other artwork.
High-resolution graphics must be scaled with twice as many pixels in each dimension to display the same size
in user space. For example, if you supply a standard-resolution image sized at 50x50 pixels, the high-resolution
version must be sized at 100x100 pixels.
For AppKit to recognize and load high-resolution versions of your graphics at appropriate times, you must
adopt the naming convention of appending @2x to the image name. For example, a standard-resolution image
named circle.png would have a high-resolution counterpart named circle@2x.png. Ideally, you can
package both image versions into a single TIFF file. This is most easily done by setting the Xcode option
Combine High Resolution Artwork to Yes in Target Build Settings under Deployment.
Supporting Common App Behaviors
Optimize for High Resolution
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
59You should create a set of icons for your app consisting of standard- and high-resolution versions of each icon
size—16x16, 32x32, 128x128, 256x256, 512x512 appending @2x to the icon image name which, by convention,
specifies the icon size in user space points. For example, an icon named icon_16x16.png would have a
high-resolution counterpart named icon_16x16@2x.png, the icon_32x32.png size would have a version
named icon_32x32@2x.png, and so on.
After you’ve created your set of app icons, place them in folder a named icon.iconset. Then you can use
the iconutil command-line tool to convert your .iconset folder into a single, deployment-ready,
high-resolution .icns file.
Use High-Resolution-Savvy Image-Loading Methods
If you follow the @2x naming convention, there are two methods available that load standard- and
high-resolution versions of an image into an NSImage object, whether or not you provide a multirepresentation
image file:
● The NSImage method imageNamed: finds resources located in the main application bundle, but not in
frameworks or plug-ins.
● The NSBundle method imageForResource: looks for resources outside as well as inside the main
bundle. Framework authors should use this method.
To create an NSImage object from a CGImageRef data type, use the initWithCGImage:size: method,
specifying the image size in points, not pixels. For custom cursors, you can pass a multirepresentation TIFF to
the NSCursor class method initWithImage:hotSpot:.
Use APIs That Support High Resolution
Cocoa apps must replace deprecated APIs with their newer counterparts. Apps that use older technologies
need to replace those technologies with newer ones. NSImage, NSView, NSWindow, and NSScreen classes
have methods that support high resolution, including methods for converting geometry, detecting scaling,
and aligning pixels. These APIs and deprecated technologiesthat youmust avoid are described in High Resolution
Guidelines for OS X .
You should also consider whether your app requires further adjustments for special scenarios, such as using
pixel-based technologies (OpenGL, Quartz bitmaps) or custom Core Animation layers. These advanced
optimization techniques are described in High Resolution Guidelines for OS X , which also provides much more
detailed information about high resolution.
Supporting Common App Behaviors
Optimize for High Resolution
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
60Prepare for Fast User Switching
Fast user switching lets users share a single machine without having to log out every time they want to access
their user account. Users share physical access to the machine, including the same keyboard, mouse, and
monitor. However, instead of logging out, a new user simply logs in and switches out the previous user.
Processes in a switched-out login session continue running as before. They can continue processing data,
communicating with the system, and drawing to the screen buffer as before. However, because they are
switched out, they do not receive input from the keyboard and mouse. Similarly, if they were to check, the
monitor would appear to be in sleep mode. As a result, it may benefit some apps to adjust their behavior while
in a switched-out state to avoid wasting system resources.
While fast userswitching is a convenient feature for users, it does provide several challengesfor app developers.
Apps that rely on exclusive access to certain resources may need to modify their behavior to live in a fast user
switching environment. For example, an app that stores temporary data in /tmp may run into problems when
a second instance running under a different user tries to modify the same files in that directory.
To support fast user switching, there are certain guidelines you should follow in developing your apps, most
of which describe safe waysto identify and share system resources. A summary of these guidelinesis asfollows:
●
Incorporate session ID information into the name of any entity that appears in a global namespace,
including file names, shared memory regions, semaphores, and named pipes.
Tagging such app-specific resources with a session ID is necessary to distinguish them from similar resources
created by apps in a different session. The Security layer of the system assigns a unique ID to each login
session. Incorporating thisID into cache file or temporary directory names can prevent namespace collisions
when creating these files. See “Supporting Fast User Switching” for information on how to get the session
ID.
● Don't assume you have exclusive access to any resource, especially TCP/IP ports and hardware devices.
● Don't assume there is only one instance of a per-user service running.
● Use file-level or range-level locks when accessing files.
● Accept and handle user switch notifications. See “User Switch Notifications” for more information.
For more information on user switching, see Multiple User Environment Programming Topics.
Supporting Common App Behaviors
Prepare for Fast User Switching
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
61Take Advantage of the Dock
The Dock is a desktop app designed to reduce desktop clutter, provide users with feedback about an app, and
allow users to switch easily between tasks. You can customize your app Dock tile by modifying the Dock icon
and adding items to the menu displayed for your app, and you can customize the Dock icon of a minimized
window.
An app’s Dock icon is, by default, the app’s icon. While your app is running, you can modify or replace the
default icon with another image that indicates the current state of your app. For example, the icon for Mail
changes when messages are waiting to be read. A badge—the red circle and number in the figure—is overlaid
onto Mail’s app icon to indicate the number of unread messages. The number changes each time Mail retrieves
more messages.
When the user holds the Control key down and clicks on a Dock tile, a menu appears. If your app does nothing
to customize the menu, the Dock tile’s menu contains a list of the app’s open documents (if any), followed by
the standard menu items Keep in Dock, Open at Login, Show in Finder, Hide, and Quit. You can add other
menu itemsto the Dock menu, eitherstatically by providing a custom menu nib file, or dynamically by overriding
the application delegate’s applicationDockMenu: method.
You can also customize a dock tile when your app is not currently running by creating a Dock tile plug-in that
can update the Dock tile icon and menu. For example, you may want to update the badge text to indicate that
new content will be available the next time the app is launched, and you may want to customize the app’s
Dock menu to deal with the new content.
For information explaining how to customize your app’s Dock icon and menu, see Dock Tile Programming
Guide .
Supporting Common App Behaviors
Take Advantage of the Dock
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
62Configuring your app properly is an important part of the development process. Mac apps use a structured
directory configuration to manage their code and resource files. And although most of the files are custom
and are there to support your app, some are required by the system or the App Store and must be configured
properly.
If you intend to sell your application through the Mac App Store or use iCloud storage, you also need to create
an explicit app ID, create provisioning profiles, and enable the correct entitlements for your application. These
procedures are explained in Tools Workflow Guide for Mac .
All of the requirements for preparing your app and distributing it on the App Store are described in the App
Store Resource Center.
Configuring Your Xcode Project
To develop a Mac app, you create an Xcode project. An Xcode Project is a repository for all the files, resources,
and information required to build your app (or one of a number of othersoftware products). A project contains
all the elements used to build your app and maintains the relationships between those elements. It contains
one or more targets, which specify how to build the software. A project defines default build settings for all
the targets in the project (each target can also specify its own build settings, which override the project build
settings).
You create a new project using the Xcode File > New > New Project menu command, which invokes the New
Project dialog. This dialog enables you to choose a template for your project, such as a Cocoa app, to name it,
and to locate it in your file system. The New Project dialog also provides options, so you can specify whether
your app uses the Cocoa document architecture or the Core Data framework. When you save your project,
Xcode lets you to create a local Git repository to enable source code control for your project.
If you have two or more closely related projects, you should create an Xcode Workspace and add your projects
to it. A workspace groups projects and other documents so you can work on them together. One project can
use the products and shared libraries of another project while building, and Xcode does indexing across the
entire workspace, extending the scope of content-aware features such as code completion.
Once you have created your project, you write and edit your code using the Xcode source editor. You also use
Xcode to build and debug your code, setting breakpoints, viewing the values of variables, stepping through
running code, and reviewing issues found during builds or code analysis.
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
63
Build-Time Configuration DetailsWhen you create a new project, it includes one or more targets, where each target specifies one build product
and the instructions for how the product is to be built. Most developers never need to change the default of
the vast majority of build settings, but there are a few basic settings that you should check for each target,
such as the deployment target (platform, OS version, and architecture), main user interface, and linked
frameworks and libraries.
You also need to set up one or more schemes to specify the targets, build configuration, and executable
configuration to use when the product specified by the target is launched. You use the project editor and the
scheme editing dialog to edit build settings and control what happens when you build and run your code.
“Building and Running Your Code” explains how to work with Xcode build settings and schemes.
For more information about using Xcode to create, configure, build, and debug your project, as well as archiving
your program to package it for distribution or submission to the Mac App Store, see Xcode 4 User Guide .
The Information Property List File
An information property list (Info.plist) file contains essential runtime-configuration information for the app.
Xcode provides a version of this file with every Mac application project and configures it with several standard
keys. Although the default keys cover several important aspects of the app’s configuration, most apps require
additional keys to specify their configuration fully.
Build-Time Configuration Details
The Information Property List File
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
64To view the contents of your Info.plist file, select it in the Groups & Files pane. Xcode displays a property
list editor similar to the one in Figure 5-1 (which is from Xcode 3.2.5). You can use this window to edit the
property values and add new key-value pairs. By default, Xcode displays a more user-friendly version of each
key name. To see the key names that Xcode adds to the Info.plist file, Control-click an item in the editor
and choose Show Raw Keys/Values from the contextual menu that appears.
Figure 5-1 The information property list editor
Xcode automatically addssome important keysto the Info.plist file of all new projects and setstheir initial
values. However, there are several keys that Mac apps use commonly to configure their launch environment
and runtime behavior. Here are some keysthat you might want to add to your app’s Info.plist file specifically
for OS X:
● LSApplicationCategoryType (required for apps distributed using the App Store)
● CFBundleDisplayName
● LSHasLocalizedDisplayName
● NSHumanReadableCopyright
● LSMinimumSystemVersion
● UTExportedTypeDeclarations
● UTImportedTypeDeclarations
Build-Time Configuration Details
The Information Property List File
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
65For detailed information about these and other keys that you can include in your app’s Info.plist file, see
Information Property List Key Reference .
The OS X Application Bundle
When you build your Mac app, Xcode packages it as a bundle. A bundle is a directory in the file system that
groupsrelated resourcestogether in one place. A Mac app bundle contains a single Contents directory, inside
of which are additional files and directories with the app’s code, resources, and localized content. Table 5-1
liststhe contents of a typical Mac app bundle, which for demonstration purposesis called MyApp. This example
is for illustrative purposes only. Some of the files listed in this table may not appear in your own application
bundles.
Table 5-1 A typical application bundle
Files Description
The executable file containing your
app’s code. The name of this file is the
same as your app name minus the
.app extension.
This file is required.
Contents/MacOS/MyApp
Also known as the information property
list file, a file containing configuration
data for the app. The system uses this
data to determine how to interact with
the app at specific times.
This file is required. For more
information, see “The Information
Property List File” (page 64).
Contents/Info.plist
Build-Time Configuration Details
The OS X Application Bundle
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
66Files Description
The English version of the app’s main
nib file. This file contains the default
interface objectsto load at app launch
time. Typically, this nib file contains
the app’s menu bar and application
delegate object. It may also contain
other controller objects that should
always be available at launch time.
(The name of the main nib file can be
changed by assigning a different value
to the NSMainNibFile key in the
Info.plist file.)
Thisfile is optional but recommended.
For more information, see “The
Information Property List File” (page
64)
Contents/Resources/English.lproj/MainMenu.nib
Nonlocalized resources are placed at
the top level of the Resources
directory (sun.png represents a
nonlocalized image file in the
example). The app uses nonlocalized
resources when no localized version
of the same resource is provided. Thus,
you can use these files in situations
where the resource is the same for all
localizations.
Contents/Resources/sun.png (or other resource files)
Build-Time Configuration Details
The OS X Application Bundle
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
67Files Description
Localized resources are placed in
subdirectories with an ISO 639-1
language abbreviation for a name plus
an .lproj suffix. Although more
human readable names (such as
English.lproj, French.lproj, and
German.lproj) can be used for
directory names, the ISO 639-1 names
are preferred because they allow you
to include an ISO 3166-1 regional
designation. (For example, the
en_GB.lproj directory contains
resources localized for English as
spoken in Great Britain, the es.lproj
directory contains resources localized
for Spanish, and the de.lproj
directory contains resources localized
for German.)
Contents/Resources/en_GB.lproj
Contents/Resources/es.lproj
Contents/Resources/de.lproj
Other language-specific project directories
A Mac app should be internationalized and have a language.lproj directory for each language it supports.
Even if you provide localized versions of your resources, though, include a default version of these files at the
top level of your Resources directory. The default version is used when a specific localization is not available.
At runtime, you can access your app’s resource files from your code using the following steps:
1. Obtain a reference to your app’s main bundle object (typically using theNSBundle class).
2. Use the methods of the bundle object to obtain a file-system path to the desired resource file.
3. Open (or access) the file and use it.
You obtain a reference to your app’s main bundle using the mainBundle class method of NSBundle. The
pathForResource:ofType: method is one of several NSBundle methods that you can use to retrieve the
location of resources. The following example shows how to locate a file called sun.png and create an image
object using it. The first line getsthe bundle object and the path to the file. The second line creates an NSImage
object that you could use to display the image in your app.
NSString* imagePath = [[NSBundle mainBundle] pathForResource:@"sun" ofType:@"png"];
NSImage* sunImage = [[NSImage alloc] initWithContentsOfFile:imagePath];
Build-Time Configuration Details
The OS X Application Bundle
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
68Note: If you prefer to use Core Foundation to access bundles, you can obtain a CFBundleRef
opaque type using the CFBundleGetMainBundle function. You can then use that opaque type
plus the Core Foundation functions to locate any bundle resources.
For information on how to access and use resources in your app, see Resource Programming Guide . For more
information about the structure of a Mac app bundle, see Bundle Programming Guide .
Internationalizing Your App
The process of preparing a project to handle content in different languages is called internationalization. The
process of converting text, images, and other content into other languages is called localization. Project
resources that are candidates for localization include:
● Code-generated text, including locale-specific aspects of date, time, and number formatting
● Static text—for example, strings you specify programmatically and display in parts of your user interface
or an HTML file containing app help
●
Icons (including your app icon) and other images when those images either contain text or have some
culture-specific meaning
● Sound files containing spoken language
● Nib files
Build-Time Configuration Details
Internationalizing Your App
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
69Users select their preferred language from the Language and Text system preference, shown in Figure 5-2.
Figure 5-2 The Language preference view
Your application bundle can include multiple language-specific resource directories. The names of these
directories consist of three components: an ISO 639-1 language code, an optional ISO 3166-1 region code, and
a .lproj suffix. For example, to designate resourceslocalized to English, the bundle would be named en.lproj.
By convention, these directories are called lproj directories.
Note: You may use ISO 639-2 language codes instead of those defined by ISO 639-1. For more
information about language and region codes, see “Language and Locale Designations” in
Internationalization Programming Topics.
Each lproj directory contains a localized copy of the app’s resource files. When you request the path to a
resource file using the NSBundle class or the CFBundleRef opaque type, the path you get back automatically
reflects the resource for the user’s preferred language.
For more information about internationalization and how you support localized content in your Mac apps, see
Internationalization Programming Topics.
Build-Time Configuration Details
Internationalizing Your App
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
70As you develop your app and your project code stabilizes, you can begin performance tuning. Of course, you
want your app to launch and respond to the user’s commands as quickly as possible. A responsive app fits
easily into the user’s workflow and feels well crafted.
Speed Up Your App’s Launch Time
You can improve your app’s performance at launch time by minimizing or deferring work until after the launch
sequence has completed. The launch of an app provides users with the first impression of your app, and it’s
something they see on a regular basis.
Your overriding goal during launch should be to display the app’s menu bar and main window and then start
responding to user commands as quickly as possible. Making your app responsive to commands quickly
provides a better experience for the user. The following sections provide some general tips on how to make
your app launch faster.
Delay Initialization Code
Many apps spend a lot of time initializing code that isn’t used until much later. Delaying the initialization of
subsystems that are not immediately needed can speed up your launch time considerably. Remember that
the goal is to display your app interface quickly, so try to initialize only the subsystems related to that goal
initially.
Once you have posted your interface, your app can continue to initialize additional subsystems as needed.
However, remember that just because your app is able to process commands does not mean you need all of
that code right away. The preferred way of initializing subsystems is on an as-needed basis. Wait until the user
executes a command that requires a particular subsystem and then initialize it. That way, if the user never
executes the command, you will not have wasted any time running the code to prepare for it.
Avoid putting a lot of extraneous initialization code in your awakeFromNib methods. The system calls the
awakeFromNib method of your main nib file before your app enters its main event loop. Use that method to
initialize the objects in that nib and to prepare your app interface. For all other initialization, use the
applicationDidFinishLaunching: method of your NSApplicationDelegate object instead. For more
information on nib files and how they are loaded, see Resource Programming Guide .
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
71
Tuning for Performance and ResponsivenessSimplify Your Main Nib File
Loading a nib file is an expensive process that can slow down your app launch time if you are not careful.
When a nib file isloaded, all of the objectsin that file are instantiated and made ready for use. The more objects
you include in your app’s main nib, the more time it takes to load that file and launch your app.
The instantiation process for objects in a nib file requires that any frameworks used by those objects must
themselves reside in memory. Thus loading a nib for a Cocoa app would likely require the loading of both the
AppKit and Foundation frameworks, if they were not already resident in memory. Similarly, if you declare a
custom class in your main nib file and that class relies on other frameworks, the system must load those
frameworks as well.
When designing your app’s main nib file, you should include only those objects needed to display your app’s
initial user interface. Usually, this would involve just your app’s menu bar and initial window. For any custom
classes you include in the nib, make sure their initialization code is as minimal as possible. Defer any
time-consuming operations or memory allocations until after the class is instantiated.
Minimize Global Variables
For both apps and frameworks, be careful not to declare global variables that require significant amounts of
initialization. The system initializes global variables before calling your app’s main routine. If you use a global
variable to declare an object, the system must call the constructor or initialization method for that object during
launch time. In general, it’s best to avoid declaring objects as global variables altogether when you can use a
pointer instead.
If you are implementing a framework or any type of reusable code module, you should also minimize the
number of global variables you declare. Each app that links to a framework acquires a copy of that framework’s
global variables. These variables might require several pages of virtual memory, which then increases the
memory footprint of the app. An increased memory footprint can lead to paging in the app, which has a
tremendous impact on performance.
One way to minimize the global variables in a framework is to store the variables in a malloc-allocated block
of memory instead. In this technique, you access the variables through a pointer to the memory, which you
store as a global variable. Another advantage of this technique is that it allows you to defer the creation of any
global variables until the first time they are actually used. See “Tips for Allocating Memory” in Memory Usage
Performance Guidelines for more information.
Tuning for Performance and Responsiveness
Speed Up Your App’s Launch Time
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
72Minimize File Access at Launch Time
Accessing a file is one of the slowest operations performed on a computer, so it is important that you do it as
little as possible, especially at launch time. There is always some file access that must occur at launch time,
such as loading your executable code and reading in your main nib file, but reducing your initial dependence
on startup files can provide significant speed improvements.
If you can delay the reading of a file until after launch time, do so. The following list includes some files whose
contents you may not need until after launch:
● Frameworks not used directly by your app—Avoid calling code that uses nonessential frameworks until
after launch.
● Nib files whose contents are not displayed immediately—Make sure your nib files and awakeFromNib:
code are not doing too much at launch time. See “Simplify Your Main Nib File” (page 72) for more
information.
● User preference files—User preferences may not be local so read them later if you can.
● Font files—Consider delaying font initialization until after the app has launched.
● Network files—Avoid reading files located on the network if at all possible.
If you must read a file at launch time, do so only once. If you need multiple pieces of data from the same file,
such as from a preferences file, consider reading all of the data once rather than accessing the file multiple
times.
Don’t Block the Main Thread
The main thread is where your app handles user events and other input, so you should keep it free as much
as possible to be responsive to the user. In particular, never use the main thread to perform long-running or
potentially unbounded tasks, such as tasks that require network access. Instead, always move those tasks onto
background threads. The preferred way to do so is to use Grand Central Dispatch (GCD) or operation objects
to perform tasks asynchronously.
For more information about doing work on background threads, see Concurrency Programming Guide .
Decrease Your App’s Code Size
In the context of performance, the more memory your app occupies, the more inefficient it is. More memory
means more memory allocations, more code, and a greater potential for paging.
Tuning for Performance and Responsiveness
Don’t Block the Main Thread
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
73Reducing your code footprint is not just a matter of turning on code optimizations in your compiler, although
that does help. You can also reduce your code footprint by organizing your code so that only the minimum
set of required functions is in memory at any given time. You implement this optimization by profiling your
code.
See “Memory Instruments” in Instruments User Guide for information about profiling your app’s memory
allocations.
Compiler-Level Optimizations
The Xcode compiler supports optimization options that let you choose whether you prefer a smaller binary
size, faster code, or faster build times. For new projects, Xcode automatically disables optimizations for the
debug build configuration and selects the Fastest, Smallest option for the release build configuration. Code
optimizations of any kind result in slower build times because of the extra work involved in the optimization
process. If your code is changing, as it does during the development cycle, you do not want optimizations
enabled. As you near the end of your development cycle, though, the release build configuration can give you
an indication of the size of your finished product, so the Fastest, Smallest option is appropriate.
Table 6-1 lists the optimization levels available in Xcode. When you select one of these options, Xcode passes
the appropriate flags to the compiler for the given group or files. These options are available at the target level
or as part of a build configuration. See the Xcode Build System Guide for information on working with build
settings for your project.
Table 6-1 Compiler optimization options
Xcode setting Description
The compiler does not attempt to optimize code. Use this option during development
when you are focused on solving logic errors and need a fast compile time. Do not
use this option for shipping your executable.
None
The compiler performs simple optimizations to boost code performance while
minimizing the impact to compile time. This option also uses more memory during
compilation.
Fast
The compiler performs nearly all supported optimizations that do not require a
space-time tradeoff. The compiler does not perform loop unrolling or function inlining
with this option. This option increases both compilation time and the performance
of generated code.
Faster
The compiler performs all optimizations in an attempt to improve the speed of the
generated code. This option can increase the size of generated code as the compiler
performs aggressive inlining of functions.
This option is generally not recommended.
Fastest
Tuning for Performance and Responsiveness
Decrease Your App’s Code Size
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
74Xcode setting Description
The compiler performs all optimizations that do not typically increase code size. This
is the preferred option for shipping code because it gives your executable a smaller
memory footprint.
Fastest,
Smallest
As with any performance enhancement, do not make assumptions about which option will give you the best
results. You should always measure the results of each optimization you try. For example, the Fastest option
might generate extremely fast code for a particular module, but it usually doesso at the expense of executable
size. Any speed advantages you gain from the code generation are easily lost if the code needs to be paged
in from disk at runtime.
Use Core Data for Large Data Sets
If your app manipulates large amounts of structured data, store it in a Core Data persistent store or in a SQLite
database instead of in a flat file. Both Core Data and SQLite provide efficient ways to manage large data sets
without requiring the entire set to be in memory all at once. Use SQLite if you deal with low-level data structures,
or an existing SQLite database. Core Data provides a high-level abstraction for efficient object-graph
management with an Objective-C interface; it is, however, an advanced framework and you shouldn't use it
until you have gained adequate experience.
For more information about Core Data, see Core Data Programming Guide and Optimizing Core Data with
Instruments.
Eliminate Memory Leaks
Your app should not have any memory leaks. You can use the Instruments app to track down leaks in your
code, both in the simulator and on actual devices. See “Memory Instruments” in Instruments User Guide for
information about finding memory leaks.
Dead Strip Your Code
For statically linked executables, dead-code stripping is the process of removing unreferenced code from the
executable file. If the code is unreferenced, it must not be used and therefore is not needed in the executable
file. Removing dead code reduces the size of your executable and can help reduce paging.
To enable dead-code stripping in Xcode, in the Linking group of Build Settings, set the Dead Code Stripping
option to Yes.
Tuning for Performance and Responsiveness
Decrease Your App’s Code Size
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
75Strip Symbol Information
Debugging symbols and dynamic-binding information can take up a lot of space and comprise a large
percentage of your executable’s size. Before shipping your code, you should strip out all unneeded symbols.
To strip debugging symbols from your executable, change the Xcode compiler code generation Generate
Debug Symbols option to No. You can also generate debugging symbols on a target-by-target basis if you
prefer. See the Xcode Help for more information on build configurations and target settings.
Tuning for Performance and Responsiveness
Decrease Your App’s Code Size
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
76This table describes the changes to Mac App Programming Guide .
Date Notes
Added a short section on adopting iCloud in a Mac app, “Integrating
iCloud Support Into Your App” (page 30).
2012-07-23
Removed the chapter on iCloud, which is superseded by iCloud Design
Guide .
Rewrote section about supporting high resolution: “Optimize for High
Resolution” (page 58).
Summarized chapter on document-based apps, added iCloud chapter,
and revised sandbox information. Made minor technical and editorial
revisions throughout. Changed title from OS X Application Programming
Guide.
2012-01-09
2011-06-27 New document describing the development process for Mac apps.
2012-07-23 | © 2012 Apple Inc. All Rights Reserved.
77
Document Revision HistoryApple Inc.
© 2012 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Bonjour, Cocoa, Finder,
Instruments, iPhoto, iTunes, Keychain, Mac,
Macintosh, Objective-C, OS X, Quartz, QuickDraw,
Sand, Spotlight, Time Machine, and Xcode are
trademarks of Apple Inc., registered in the U.S.
and other countries.
Launchpad is a trademark of Apple Inc.
iCloud is a service mark of Apple Inc., registered
in the U.S. and other countries.
App Store and Mac App Store are service marks
of Apple Inc.
OpenGL is a registered trademark of Silicon
Graphics, Inc.
UNIX is a registered trademark of The Open
Group.
iOS is a trademark or registered trademark of
Cisco in the U.S. and other countries and is used
under license.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
Objective-C Runtime
Programming GuideContents
Introduction 5
Organization of This Document 5
See Also 5
Runtime Versions and Platforms 7
Legacy and Modern Versions 7
Platforms 7
Interacting with the Runtime 8
Objective-C Source Code 8
NSObject Methods 8
Runtime Functions 9
Messaging 10
The objc_msgSend Function 10
Using Hidden Arguments 13
Getting a Method Address 14
Dynamic Method Resolution 16
Dynamic Method Resolution 16
Dynamic Loading 17
Message Forwarding 18
Forwarding 18
Forwarding and Multiple Inheritance 20
Surrogate Objects 21
Forwarding and Inheritance 21
Type Encodings 24
Declared Properties 28
Property Type and Functions 28
Property Type String 29
Property Attribute Description Examples 30
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
2Document Revision History 33
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
3
ContentsFigures and Tables
Messaging 10
Figure 3-1 Messaging Framework 12
Message Forwarding 18
Figure 5-1 Forwarding 20
Type Encodings 24
Table 6-1 Objective-C type encodings 24
Table 6-2 Objective-C method encodings 26
Declared Properties 28
Table 7-1 Declared property type encodings 30
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
4The Objective-C language defers as many decisions as it can from compile time and link time to runtime.
Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but
also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system
for the Objective-C language; it’s what makes the language work.
This document looks at the NSObject class and how Objective-C programs interact with the runtime system.
In particular, it examines the paradigms for dynamically loading new classes at runtime, and forwarding
messages to other objects. It also provides information about how you can find information about objects
while your program is running.
You should read this document to gain an understanding of how the Objective-C runtime system works and
how you can take advantage of it. Typically, though, there should be little reason for you to need to know and
understand this material to write a Cocoa application.
Organization of This Document
This document has the following chapters:
●
“Runtime Versions and Platforms” (page 7)
●
“Interacting with the Runtime” (page 8)
●
“Messaging” (page 10)
●
“Dynamic Method Resolution” (page 16)
●
“Message Forwarding” (page 18)
●
“Type Encodings” (page 24)
●
“Declared Properties” (page 28)
See Also
Objective-C Runtime Reference describes the data structures and functions of the Objective-C runtime support
library. Your programs can use these interfaces to interact with the Objective-C runtime system. For example,
you can add classes or methods, or obtain a list of all class definitions for loaded classes.
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
5
IntroductionThe Objective-C Programming Language describes the Objective-C language.
Objective-C Release Notes describes some of the changes in the Objective-C runtime in the latest release of OS
X.
Introduction
See Also
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
6There are different versions of the Objective-C runtime on different platforms.
Legacy and Modern Versions
There are two versions of the Objective-C runtime—“modern” and “legacy”. The modern version wasintroduced
with Objective-C 2.0 and includes a number of new features. The programming interface for the legacy version
of the runtime is described in Objective-C 1 Runtime Reference ; the programming interface for the modern
version of the runtime is described in Objective-C Runtime Reference .
The most notable new feature is that instance variables in the modern runtime are “non-fragile”:
●
In the legacy runtime, if you change the layout of instance variables in a class, you must recompile classes
that inherit from it.
●
In the modern runtime, if you change the layout of instance variables in a class, you do not have to
recompile classes that inherit from it.
In addition, the modern runtime supports instance variable synthesis for declared properties (see Declared
Properties in The Objective-C Programming Language ).
Platforms
iPhone applications and 64-bit programs on OS X v10.5 and later use the modern version of the runtime.
Other programs (32-bit programs on OS X desktop) use the legacy version of the runtime.
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
7
Runtime Versions and PlatformsObjective-C programs interact with the runtime system at three distinct levels: through Objective-C source
code; through methods defined in the NSObject class of the Foundation framework; and through direct calls
to runtime functions.
Objective-C Source Code
For the most part, the runtime system works automatically and behind the scenes. You use it just by writing
and compiling Objective-C source code.
When you compile code containing Objective-C classes and methods, the compiler creates the data structures
and function calls that implement the dynamic characteristics of the language. The data structures capture
information found in class and category definitions and in protocol declarations; they include the class and
protocol objects discussed in Defining a Class and Protocols in The Objective-C Programming Language , as well
as method selectors, instance variable templates, and other information distilled from source code. The principal
runtime function is the one that sends messages, as described in “Messaging” (page 10). It’s invoked by
source-code message expressions.
NSObject Methods
Most objects in Cocoa are subclasses of the NSObject class, so most objects inherit the methods it defines.
(The notable exception is the NSProxy class; see “Message Forwarding” (page 18) for more information.) Its
methods therefore establish behaviors that are inherent to every instance and every class object. However, in
a few cases, the NSObject class merely defines a template for how something should be done; it doesn’t
provide all the necessary code itself.
For example, the NSObject class defines a description instance method that returns a string describing
the contents of the class. This is primarily used for debugging—the GDB print-object command prints the
string returned from this method. NSObject’s implementation of this method doesn’t know what the class
contains,so it returns a string with the name and address of the object. Subclasses of NSObject can implement
this method to return more details. For example, the Foundation class NSArray returns a list of descriptions
of the objects it contains.
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
8
Interacting with the RuntimeSome of the NSObject methodssimply query the runtime system for information. These methods allow objects
to perform introspection. Examples of such methods are the class method, which asks an object to identify
its class; isKindOfClass: and isMemberOfClass:, which test an object’s position in the inheritance
hierarchy; respondsToSelector:, which indicates whether an object can accept a particular message;
conformsToProtocol:, which indicates whether an object claims to implement the methods defined in a
specific protocol; and methodForSelector:, which provides the address of a method’s implementation.
Methods like these give an object the ability to introspect about itself.
Runtime Functions
The runtime system is a dynamic shared library with a public interface consisting of a set of functions and data
structuresin the header fileslocated within the directory /usr/include/objc. Many of these functions allow
you to use plain C to replicate what the compiler does when you write Objective-C code. Others form the basis
for functionality exported through the methods of the NSObject class. These functions make it possible to
develop other interfacesto the runtime system and produce toolsthat augment the development environment;
they’re not needed when programming in Objective-C. However, a few of the runtime functions might on
occasion be useful when writing an Objective-C program. All of these functions are documented in Objective-C
Runtime Reference .
Interacting with the Runtime
Runtime Functions
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
9This chapter describes how the message expressions are converted into objc_msgSend function calls, and
how you can refer to methods by name. It then explains how you can take advantage of objc_msgSend, and
how—if you need to—you can circumvent dynamic binding.
The objc_msgSend Function
In Objective-C, messages aren’t bound to method implementations until runtime. The compiler converts a
message expression,
[receiver message]
into a call on a messaging function, objc_msgSend. This function takes the receiver and the name of the
method mentioned in the message—that is, the method selector—as its two principal parameters:
objc_msgSend(receiver, selector)
Any arguments passed in the message are also handed to objc_msgSend:
objc_msgSend(receiver, selector, arg1, arg2, ...)
The messaging function does everything necessary for dynamic binding:
●
It first finds the procedure (method implementation) that the selector refers to. Since the same method
can be implemented differently by separate classes, the precise procedure that it finds depends on the
class of the receiver.
●
It then calls the procedure, passing it the receiving object (a pointer to its data), along with any arguments
that were specified for the method.
● Finally, it passes on the return value of the procedure as its own return value.
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
10
MessagingNote: The compiler generates calls to the messaging function. You should never call it directly in
the code you write.
The key to messaging lies in the structures that the compiler builds for each class and object. Every class
structure includes these two essential elements:
● A pointer to the superclass.
● A class dispatch table. This table has entries that associate method selectors with the class-specific
addresses of the methods they identify. The selector for the setOrigin:: method is associated with the
address of (the procedure that implements) setOrigin::, the selector for the display method is
associated with display’s address, and so on.
When a new object is created, memory for it is allocated, and its instance variables are initialized. First among
the object’s variables is a pointer to its class structure. This pointer, called isa, gives the object access to its
class and, through the class, to all the classes it inherits from.
Messaging
The objc_msgSend Function
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
11Note: While not strictly a part of the language, the isa pointer is required for an object to work
with the Objective-C runtime system. An object needsto be “equivalent” to a struct objc_object
(defined in objc/objc.h) in whatever fieldsthe structure defines. However, you rarely, if ever, need
to create your own root object, and objects that inherit from NSObject or NSProxy automatically
have the isa variable.
These elements of class and object structure are illustrated in Figure 3-1.
Figure 3-1 Messaging Framework
. . .
superclass
selector...address
selector...address
selector...address
. . .
superclass
selector...address
selector...address
selector...address
. . .
superclass
selector...address
selector...address
selector...address
isa
instance variable
instance variable
. . .
The object’s superclass
The root class (NSObject)
The object’s class
Messaging
The objc_msgSend Function
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
12When a message is sent to an object, the messaging function follows the object’s isa pointer to the class
structure where it looks up the method selector in the dispatch table. If it can’t find the selector there,
objc_msgSend followsthe pointer to the superclass and triesto find the selector in its dispatch table. Successive
failures cause objc_msgSend to climb the class hierarchy until it reaches the NSObject class. Once it locates
the selector, the function callsthe method entered in the table and passesit the receiving object’s data structure.
This is the way that method implementations are chosen at runtime—or, in the jargon of object-oriented
programming, that methods are dynamically bound to messages.
To speed the messaging process, the runtime system caches the selectors and addresses of methods as they
are used. There’s a separate cache for each class, and it can contain selectors for inherited methods as well as
for methods defined in the class. Before searching the dispatch tables, the messaging routine first checks the
cache of the receiving object’s class(on the theory that a method that was used once may likely be used again).
If the method selector is in the cache, messaging is only slightly slower than a function call. Once a program
has been running long enough to “warm up” its caches, almost all the messagesitsendsfind a cached method.
Caches grow dynamically to accommodate new messages as the program runs.
Using Hidden Arguments
When objc_msgSend finds the procedure that implements a method, it calls the procedure and passes it all
the arguments in the message. It also passes the procedure two hidden arguments:
● The receiving object
● The selector for the method
These arguments give every method implementation explicit information about the two halves of the message
expression that invoked it. They’re said to be “hidden” because they aren’t declared in the source code that
defines the method. They’re inserted into the implementation when the code is compiled.
Although these arguments aren’t explicitly declared, source code can still refer to them (just as it can refer to
the receiving object’s instance variables). A method refers to the receiving object as self, and to its own
selector as _cmd. In the example below, _cmd refers to the selector for the strange method and self to the
object that receives a strange message.
- strange
{
id target = getTheReceiver();
SEL method = getTheMethod();
Messaging
Using Hidden Arguments
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
13if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
self is the more useful of the two arguments. It is, in fact, the way the receiving object’s instance variables
are made available to the method definition.
Getting a Method Address
The only way to circumvent dynamic binding is to get the address of a method and call it directly as if it were
a function. This might be appropriate on the rare occasions when a particular method will be performed many
times in succession and you want to avoid the overhead of messaging each time the method is performed.
With a method defined in the NSObject class, methodForSelector:, you can ask for a pointer to the
procedure that implements a method, then use the pointer to call the procedure. The pointer that
methodForSelector: returns must be carefully cast to the proper function type. Both return and argument
types should be included in the cast.
The example below shows how the procedure that implements the setFilled: method might be called:
void (*setter)(id, SEL, BOOL);
int i;
setter = (void (*)(id, SEL, BOOL))[target
methodForSelector:@selector(setFilled:)];
for ( i = 0; i < 1000, i++ )
setter(targetList[i], @selector(setFilled:), YES);
The first two arguments passed to the procedure are the receiving object (self) and the method selector
(_cmd). These arguments are hidden in method syntax but must be made explicit when the method is called
as a function.
Using methodForSelector: to circumvent dynamic binding saves most of the time required by messaging.
However, the savings will be significant only where a particular message is repeated many times, as in the for
loop shown above.
Messaging
Getting a Method Address
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
14Note that methodForSelector: is provided by the Cocoa runtime system; it’s not a feature of the Objective-C
language itself.
Messaging
Getting a Method Address
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
15This chapter describes how you can provide an implementation of a method dynamically.
Dynamic Method Resolution
There are situations where you might want to provide an implementation of a method dynamically. For example,
the Objective-C declared propertiesfeature (see Declared Properties in The Objective-C Programming Language )
includes the @dynamic directive:
@dynamic propertyName;
which tells the compiler that the methods associated with the property will be provided dynamically.
You can implement the methods resolveInstanceMethod: and resolveClassMethod: to dynamically
provide an implementation for a given selector for an instance and class method respectively.
An Objective-C method is simply a C function that take at least two arguments—self and _cmd. You can add
a function to a class as a method using the function class_addMethod. Therefore, given the following function:
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
you can dynamically add it to a class as a method (called resolveThisMethodDynamically) using
resolveInstanceMethod: like this:
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
16
Dynamic Method Resolution}
return [super resolveInstanceMethod:aSEL];
}
@end
Forwarding methods (as described in “Message Forwarding” (page 18)) and dynamic method resolution are,
largely, orthogonal. A class has the opportunity to dynamically resolve a method before the forwarding
mechanism kicksin. If respondsToSelector: or instancesRespondToSelector: isinvoked, the dynamic
method resolver is given the opportunity to provide an IMP for the selector first. If you implement
resolveInstanceMethod: but want particular selectors to actually be forwarded via the forwarding
mechanism, you return NO for those selectors.
Dynamic Loading
An Objective-C program can load and link new classes and categories while it’s running. The new code is
incorporated into the program and treated identically to classes and categories loaded at the start.
Dynamic loading can be used to do a lot of different things. For example, the various modules in the System
Preferences application are dynamically loaded.
In the Cocoa environment, dynamic loading is commonly used to allow applications to be customized. Others
can write modules that your program loads at runtime—much as Interface Builder loads custom palettes and
the OS X System Preferences application loads custom preference modules. The loadable modules extend
what your application can do. They contribute to it in ways that you permit but could not have anticipated or
defined yourself. You provide the framework, but others provide the code.
Although there is a runtime function that performs dynamic loading of Objective-C modules in Mach-O files
(objc_loadModules, defined in objc/objc-load.h), Cocoa’s NSBundle class provides a significantly more
convenient interface for dynamic loading—one that’s object-oriented and integrated with related services.
See the NSBundle classspecification in the Foundation framework reference for information on the NSBundle
class and its use. See OS X ABI Mach-O File Format Reference for information on Mach-O files.
Dynamic Method Resolution
Dynamic Loading
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
17Sending a message to an object that does not handle that message is an error. However, before announcing
the error, the runtime system gives the receiving object a second chance to handle the message.
Forwarding
If you send a message to an object that does not handle that message, before announcing an error the runtime
sends the object a forwardInvocation: message with an NSInvocation object as its sole argument—the
NSInvocation object encapsulates the original message and the arguments that were passed with it.
You can implement a forwardInvocation: method to give a default response to the message, or to avoid
the error in some other way. As its name implies, forwardInvocation: is commonly used to forward the
message to another object.
To see the scope and intent of forwarding, imagine the following scenarios: Suppose, first, that you’re designing
an object that can respond to a message called negotiate, and you want itsresponse to include the response
of another kind of object. You could accomplish this easily by passing a negotiate message to the other
object somewhere in the body of the negotiate method you implement.
Take this a step further, and suppose that you want your object’s response to a negotiate message to be
exactly the response implemented in another class. One way to accomplish this would be to make your class
inherit the method from the other class. However, it might not be possible to arrange things this way. There
may be good reasons why your class and the class that implements negotiate are in different branches of
the inheritance hierarchy.
Even if your class can’t inherit the negotiate method, you can still “borrow” it by implementing a version of
the method that simply passes the message on to an instance of the other class:
- negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
18
Message ForwardingThis way of doing things could get a little cumbersome, especially if there were a number of messages you
wanted your object to pass on to the other object. You’d have to implement one method to cover each method
you wanted to borrow from the other class. Moreover, it would be impossible to handle cases where you didn’t
know, at the time you wrote the code, the full set of messages you might want to forward. That set might
depend on events at runtime, and it might change as new methods and classes are implemented in the future.
The second chance offered by a forwardInvocation: message provides a less ad hoc solution to this
problem, and one that’s dynamic rather than static. It workslike this: When an object can’t respond to a message
because it doesn’t have a method matching the selector in the message, the runtime system informsthe object
by sending it a forwardInvocation: message. Every object inherits a forwardInvocation: method from
the NSObject class. However, NSObject’s version of the method simply invokes
doesNotRecognizeSelector:. By overriding NSObject’s version and implementing your own, you can
take advantage of the opportunity that the forwardInvocation: message provides to forward messages
to other objects.
To forward a message, all a forwardInvocation: method needs to do is:
● Determine where the message should go, and
● Send it there with its original arguments.
The message can be sent with the invokeWithTarget: method:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
The return value of the message that’s forwarded is returned to the original sender. All types of return values
can be delivered to the sender, including ids, structures, and double-precision floating-point numbers.
A forwardInvocation: method can act as a distribution center for unrecognized messages, parceling them
out to different receivers. Or it can be a transfer station, sending all messages to the same destination. It can
translate one message into another, or simply “swallow” some messages so there’s no response and no error.
A forwardInvocation: method can also consolidate several messages into a single response. What
forwardInvocation: doesis up to the implementor. However, the opportunity it providesfor linking objects
in a forwarding chain opens up possibilities for program design.
Message Forwarding
Forwarding
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
19Note: The forwardInvocation: method gets to handle messages only if they don’t invoke an
existing method in the nominal receiver. If, for example, you want your object to forward negotiate
messages to another object, it can’t have a negotiate method of its own. If it does, the message
will never reach forwardInvocation:.
For more information on forwarding and invocations, see the NSInvocation class specification in the
Foundation framework reference.
Forwarding and Multiple Inheritance
Forwarding mimics inheritance, and can be used to lend some of the effects of multiple inheritance to
Objective-C programs. As shown in Figure 5-1 (page 20), an object that responds to a message by forwarding
it appears to borrow or “inherit” a method implementation defined in another class.
Figure 5-1 Forwarding
isa
. . .
– forwardInvocation: – negotiate
negotiate isa
. . .
Warrior Diplomat
In this illustration, an instance of the Warrior class forwards a negotiate message to an instance of the
Diplomat class. The Warrior will appear to negotiate like a Diplomat. It will seem to respond to the negotiate
message, and for all practical purposes it does respond (although it’s really a Diplomat that’s doing the work).
The object that forwards a message thus“inherits” methodsfrom two branches of the inheritance hierarchy—its
own branch and that of the object that responds to the message. In the example above, it appears as if the
Warrior class inherits from Diplomat as well as its own superclass.
Message Forwarding
Forwarding and Multiple Inheritance
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
20Forwarding provides most of the features that you typically want from multiple inheritance. However, there’s
an important difference between the two: Multiple inheritance combines different capabilities in a single
object. It tends toward large, multifaceted objects. Forwarding, on the other hand, assigns separate
responsibilitiesto disparate objects. It decomposes problemsinto smaller objects, but associatesthose objects
in a way that’s transparent to the message sender.
Surrogate Objects
Forwarding not only mimics multiple inheritance, it also makes it possible to develop lightweight objects that
represent or “cover” more substantial objects. The surrogate standsin for the other object and funnels messages
to it.
The proxy discussed in “Remote Messaging” in The Objective-C Programming Language is such a surrogate. A
proxy takes care of the administrative details of forwarding messages to a remote receiver, making sure
argument values are copied and retrieved across the connection, and so on. But it doesn’t attempt to do much
else; it doesn’t duplicate the functionality of the remote object but simply gives the remote object a local
address, a place where it can receive messages in another application.
Other kinds of surrogate objects are also possible. Suppose, for example, that you have an object that
manipulates a lot of data—perhaps it creates a complicated image or reads the contents of a file on disk.
Setting this object up could be time-consuming, so you prefer to do it lazily—when it’s really needed or when
system resources are temporarily idle. At the same time, you need at least a placeholder for this object in order
for the other objects in the application to function properly.
In this circumstance, you could initially create, not the full-fledged object, but a lightweight surrogate for it.
This object could do some things on its own, such as answer questions about the data, but mostly it would
just hold a place for the larger object and, when the time came, forward messages to it. When the surrogate’s
forwardInvocation: method first receives a message destined for the other object, it would ensure that
the object existed and would create it if it didn’t. All messages for the larger object go through the surrogate,
so, as far as the rest of the program is concerned, the surrogate and the larger object would be the same.
Forwarding and Inheritance
Although forwarding mimics inheritance, the NSObject class never confuses the two. Methods like
respondsToSelector: and isKindOfClass: look only at the inheritance hierarchy, never at the forwarding
chain. If, for example, a Warrior object is asked whether it responds to a negotiate message,
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
Message Forwarding
Surrogate Objects
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
21the answer is NO, even though it can receive negotiate messages without error and respond to them, in a
sense, by forwarding them to a Diplomat. (See Figure 5-1 (page 20).)
In many cases, NO is the right answer. But it may not be. If you use forwarding to set up a surrogate object or
to extend the capabilities of a class, the forwarding mechanism should probably be astransparent asinheritance.
If you want your objects to act as if they truly inherited the behavior of the objects they forward messages to,
you’ll need to re-implement the respondsToSelector: and isKindOfClass: methods to include your
forwarding algorithm:
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ( [super respondsToSelector:aSelector] )
return YES;
else {
/* Here, test whether the aSelector message can *
* be forwarded to another object and whether that *
* object can respond to it. Return YES if it can. */
}
return NO;
}
In addition to respondsToSelector: and isKindOfClass:,the instancesRespondToSelector:method
should also mirror the forwarding algorithm. If protocols are used, the conformsToProtocol: method should
likewise be added to the list. Similarly, if an object forwards any remote messages it receives, it should have a
version of methodSignatureForSelector: that can return accurate descriptions of the methods that
ultimately respond to the forwarded messages; for example, if an object is able to forward a message to its
surrogate, you would implement methodSignatureForSelector: as follows:
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
Message Forwarding
Forwarding and Inheritance
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
22You might consider putting the forwarding algorithm somewhere in private code and have all these methods,
forwardInvocation: included, call it.
Note: Thisis an advanced technique,suitable only forsituations where no othersolution is possible.
It is not intended as a replacement for inheritance. If you must make use of this technique, make
sure you fully understand the behavior of the class doing the forwarding and the class you’re
forwarding to.
The methods mentioned in this section are described in the NSObject class specification in the Foundation
framework reference. For information on invokeWithTarget:, see the NSInvocation class specification
in the Foundation framework reference.
Message Forwarding
Forwarding and Inheritance
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
23To assist the runtime system, the compiler encodes the return and argument types for each method in a
character string and associates the string with the method selector. The coding scheme it uses is also useful
in other contexts and so is made publicly available with the @encode() compiler directive. When given a type
specification, @encode() returns a string encoding that type. The type can be a basic type such as an int, a
pointer, a tagged structure or union, or a class name—any type, in fact, that can be used as an argument to
the C sizeof() operator.
char *buf1 = @encode(int **);
char *buf2 = @encode(struct key);
char *buf3 = @encode(Rectangle);
The table below lists the type codes. Note that many of them overlap with the codes you use when encoding
an object for purposes of archiving or distribution. However, there are codes listed here that you can’t use
when writing a coder, and there are codesthat you may want to use when writing a coder that aren’t generated
by @encode(). (See the NSCoder class specification in the Foundation Framework reference for more
information on encoding objects for archiving or distribution.)
Table 6-1 Objective-C type encodings
Code Meaning
c A char
i An int
s A short
A long
l is treated as a 32-bit quantity on 64-bit programs.
l
q A long long
C An unsigned char
I An unsigned int
S An unsigned short
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
24
Type EncodingsCode Meaning
L An unsigned long
Q An unsigned long long
f A float
d A double
B A C++ bool or a C99 _Bool
v A void
* A character string (char *)
@ An object (whether statically typed or typed id)
# A class object (Class)
: A method selector (SEL)
[array type ] An array
{name=type...} A structure
(name=type...) A union
bnum A bit field of num bits
^type A pointer to type
? An unknown type (among other things, this code is used for function pointers)
Important: Objective-C does not support the long double type. @encode(long double) returns d,
which is the same encoding as for double.
The type code for an array is enclosed within square brackets; the number of elements in the array is specified
immediately after the open bracket, before the array type. For example, an array of 12 pointers to floats
would be encoded as:
[12^f]
Structures are specified within braces, and unions within parentheses. The structure tag is listed first, followed
by an equal sign and the codes for the fields of the structure listed in sequence. For example, the structure
Type Encodings
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
25typedef struct example {
id anObject;
char *aString;
int anInt;
} Example;
would be encoded like this:
{example=@*i}
The same encoding results whether the defined type name (Example) or the structure tag (example) is passed
to @encode(). The encoding for a structure pointer carriesthe same amount of information about the structure’s
fields:
^{example=@*i}
However, another level of indirection removes the internal type specification:
^^{example}
Objects are treated like structures. For example, passing the NSObject class name to @encode() yields this
encoding:
{NSObject=#}
The NSObject class declares just one instance variable, isa, of type Class.
Note that although the @encode() directive doesn’t return them, the runtime system uses the additional
encodings listed in Table 6-2 for type qualifiers when they’re used to declare methods in a protocol.
Table 6-2 Objective-C method encodings
Code Meaning
r const
n in
N inout
Type Encodings
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
26Code Meaning
o out
O bycopy
R byref
V oneway
Type Encodings
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
27When the compiler encounters property declarations (see Declared Properties in The Objective-C Programming
Language ), it generates descriptive metadata that is associated with the enclosing class, category or protocol.
You can accessthis metadata using functionsthatsupport looking up a property by name on a class or protocol,
obtaining the type of a property as an @encode string, and copying a list of a property's attributes as an array
of C strings. A list of declared properties is available for each class and protocol.
Property Type and Functions
The Property structure defines an opaque handle to a property descriptor.
typedef struct objc_property *Property;
You can use the functions class_copyPropertyList and protocol_copyPropertyList to retrieve an
array of the properties associated with a class (including loaded categories) and a protocol respectively:
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
For example, given the following class declaration:
@interface Lender : NSObject {
float alone;
}
@property float alone;
@end
you can get the list of properties using:
id LenderClass = objc_getClass("Lender");
unsigned int outCount;
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
28
Declared Propertiesobjc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
You can use the property_getName function to discover the name of a property:
const char *property_getName(objc_property_t property)
You can use the functions class_getProperty and protocol_getProperty to get a reference to a property
with a given name in a class and protocol respectively:
objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL
isRequiredProperty, BOOL isInstanceProperty)
You can use the property_getAttributes function to discover the name and the @encode type string of
a property. For details of the encoding type strings, see “Type Encodings” (page 24); for details of this string,
see “Property Type String” (page 29) and “Property Attribute Description Examples” (page 30).
const char *property_getAttributes(objc_property_t property)
Putting these together, you can print a list of all the properties associated with a class using the following
code:
id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "%s %s\n", property_getName(property),
property_getAttributes(property));
}
Property Type String
You can use the property_getAttributes function to discover the name, the @encode type string of a
property, and other attributes of the property.
Declared Properties
Property Type String
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
29The string starts with a T followed by the @encode type and a comma, and finishes with a V followed by the
name of the backing instance variable. Between these, the attributes are specified by the following descriptors,
separated by commas:
Table 7-1 Declared property type encodings
Code Meaning
R The property is read-only (readonly).
C The property is a copy of the value last assigned (copy).
& The property is a reference to the value last assigned (retain).
N The property is non-atomic (nonatomic).
The property defines a custom getter selector name. The name follows the G (for
example, GcustomGetter,).
G
The property defines a custom setter selector name. The name follows the S (for
example, ScustomSetter:,).
S
D The property is dynamic (@dynamic).
W The property is a weak reference (__weak).
P The property is eligible for garbage collection.
t Specifies the type using old-style encoding.
For examples, see “Property Attribute Description Examples” (page 30).
Property Attribute Description Examples
Given these definitions:
enum FooManChu { FOO, MAN, CHU };
struct YorkshireTeaStruct { int pot; char lady; };
typedef struct YorkshireTeaStruct YorkshireTeaStructType;
union MoneyUnion { float alone; double down; };
Declared Properties
Property Attribute Description Examples
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
30the following table shows sample property declarations and the corresponding string returned by
property_getAttributes:
Property declaration Property description
@property char charDefault; Tc,VcharDefault
@property double doubleDefault; Td,VdoubleDefault
@property enum FooManChu enumDefault; Ti,VenumDefault
@property float floatDefault; Tf,VfloatDefault
@property int intDefault; Ti,VintDefault
@property long longDefault; Tl,VlongDefault
@property short shortDefault; Ts,VshortDefault
@property signed signedDefault; Ti,VsignedDefault
T{YorkshireTeaStruct="pot"i"lady"c},VstructDefault
@property struct YorkshireTeaStruct
structDefault;
T{YorkshireTeaStruct="pot"i"lady"c},VtypedefDefault
@property YorkshireTeaStructType
typedefDefault;
T(MoneyUnion="alone"f"down"d),VunionDefault
@property union MoneyUnion unionDefault;
@property unsigned unsignedDefault; TI,VunsignedDefault
@property int (*functionPointerDefault)(char T^?,VfunctionPointerDefault
*);
@property id idDefault; T@,VidDefault
Note: the compiler warns: no 'assign', 'retain',
or 'copy' attribute is specified - 'assign'
is assumed"
@property int *intPointer; T^i,VintPointer
@property void *voidPointerDefault; T^v,VvoidPointerDefault
Declared Properties
Property Attribute Description Examples
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
31Property declaration Property description
@property int intSynthEquals; Ti,V_intSynthEquals
In the implementation block:
@synthesize intSynthEquals=_intSynthEquals;
Ti,GintGetFoo,SintSetFoo:
,VintSetterGetter
@property(getter=intGetFoo,
setter=intSetFoo:) int intSetterGetter;
@property(readonly) int intReadonly; Ti,R,VintReadonly
@property(getter=isIntReadOnlyGetter, Ti,R,GisIntReadOnlyGetter
readonly) int intReadonlyGetter;
@property(readwrite) int intReadwrite; Ti,VintReadwrite
@property(assign) int intAssign; Ti,VintAssign
@property(retain)ididRetain; T@,&,VidRetain
@property(copy)ididCopy; T@,C,VidCopy
@property(nonatomic) int intNonatomic; Ti,VintNonatomic
@property(nonatomic, readonly, copy) id T@,R,C,VidReadonlyCopyNonatomic
idReadonlyCopyNonatomic;
T@,R,&,VidReadonlyRetainNonatomic
@property(nonatomic, readonly, retain) id
idReadonlyRetainNonatomic;
Declared Properties
Property Attribute Description Examples
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
32This table describes the changes to Objective-C Runtime Programming Guide .
Date Notes
2009-10-19 Made minor editorial changes.
2009-07-14 Completed list of types described by property_getAttributes.
2009-02-04 Corrected typographical errors.
2008-11-19 New document that describesthe Objective-C 2.0 runtime support library.
2009-10-19 | © 2009 Apple Inc. All Rights Reserved.
33
Document Revision HistoryApple Inc.
© 2009 Apple Inc.
All rights reserved.
No part of this publication may be reproduced,
stored in a retrievalsystem, or transmitted, in any
form or by any means, mechanical, electronic,
photocopying, recording, or otherwise, without
prior written permission of Apple Inc., with the
following exceptions: Any person is hereby
authorized to store documentation on a single
computer for personal use only and to print
copies of documentation for personal use
provided that the documentation contains
Apple’s copyright notice.
No licenses, express or implied, are granted with
respect to any of the technology described in this
document. Apple retains all intellectual property
rights associated with the technology described
in this document. This document is intended to
assist application developers to develop
applications only for Apple-labeled computers.
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
408-996-1010
Apple, the Apple logo, Cocoa, iPhone, Mac,
Objective-C, and OS X are trademarks of Apple
Inc., registered in the U.S. and other countries.
Even though Apple has reviewed this document,
APPLE MAKES NO WARRANTY OR REPRESENTATION,
EITHER EXPRESS OR IMPLIED, WITH RESPECT TO THIS
DOCUMENT, ITS QUALITY, ACCURACY,
MERCHANTABILITY, OR FITNESS FOR A PARTICULAR
PURPOSE.ASARESULT, THISDOCUMENT IS PROVIDED
“AS IS,” AND YOU, THE READER, ARE ASSUMING THE
ENTIRE RISK AS TO ITS QUALITY AND ACCURACY.
IN NO EVENT WILL APPLE BE LIABLE FOR DIRECT,
INDIRECT, SPECIAL, INCIDENTAL,OR CONSEQUENTIAL
DAMAGES RESULTING FROM ANY DEFECT OR
INACCURACY IN THIS DOCUMENT, even if advised of
the possibility of such damages.
THE WARRANTY AND REMEDIES SET FORTH ABOVE
ARE EXCLUSIVE AND IN LIEU OF ALL OTHERS, ORAL
OR WRITTEN, EXPRESS OR IMPLIED. No Apple dealer,
agent, or employee is authorized to make any
modification, extension, or addition to this warranty.
Some states do not allow the exclusion or limitation
of implied warranties or liability for incidental or
consequential damages, so the above limitation or
exclusion may not apply to you. This warranty gives
you specific legal rights, and you may also have other
rights which vary from state to state.
Universal Binary Programming Guidelines,
Second Edition
(Legacy)Contents
Introduction 8
Who Should Read This Document? 8
Organization of This Document 8
Assumptions 9
Conventions 10
Building a Universal Binary 11
Build Assumptions 11
Building Your Code 12
Debugging 16
Troubleshooting Your Built Application 16
Determining Whether a Binary Is Universal 18
Build Options 18
Default Compiler Options 19
Architecture-Specific Options 19
Autoconf Macros 20
See Also 20
Architectural Differences 21
Alignment 21
Bit Fields 21
Byte Order 21
Calling Conventions 22
Code on the Stack: Disabling Execution 22
Data Type Conversions 23
Data Types 23
Divide-By-Zero Operations 24
Extensible Firmware Interface (EFI) 24
Floating-Point Equality Comparisons 24
Structures and Unions 25
See Also 25
Swapping Bytes 26
Why Byte Ordering Matters 26
Retired Document | 2009-02-04 | © 2005, 2009 Apple Inc. All Rights Reserved.
2Guidelines for Swapping Bytes 28
Byte-Swapping Routines 29
Byte-Swapping Strategies 30
Constants 30
Custom Apple Event Data 31
Custom Resource Data 31
Floating-Point Values 32
Integers 33
Network-Related Data 34
OSType-to-String Conversions 35
Unicode Text Files 36
Values in an Array 38
Writing a Callback to Swap Data Bytes 38
See Also 45
Guidelines for Specific Scenarios 46
Aliases 46
Archived Bit Fields 46
Automator Scripts 46
Bit Shifting 47
Bit Test, Set, and Clear Functions: Carbon and POSIX 47